雑記帳
2011-10-04 (Tue) [長年日記]
■ [Apache] 携帯電話っぽいUserAgentからのアクセスを携帯電話のネットワーク以外から拒否する設定
携帯電話ではないネットワークから、携帯電話(いわゆるフィーチャーフォン)っぽいUserAgentからアクセスがあった場合に接続を拒否したい、という要件があったので、作成してみた。
https://gist.github.com/1260821
単なる力技で、正規表現的に整理されていないので、誰か綺麗にして欲しい。
これを作成するときに調べたら、mod_rewriteにCIDRを理解させるパッチを見つけた。これがオリジナルで使えれば楽なんだけど…。
2011-10-05 (Wed) [長年日記]
■ [Linux][vsftpd] vsftpdでユーザごとにディレクトリを変更する方法
FTPでログインした際に、ユーザの通常のホームディレクトリとは別の所を初期ディレクトリとしたいという要望があったので、調べてみた。
vsftpdではユーザごとに設定ファイルを変更できる機能があり、それを使うことで実現することができた。
まずは、ユーザごとに設定ファイルが使えるようにする。
user_config_dir=/etc/vsftpd/vsftpd_user_conf
そして、上記で指定したディレクトリに、対象となるユーザ名でファイルを作成し、local_rootの設定を追記する。
$ touch/etc/vsftpd/vsftpd_user_conf/example_user local_root=./path/to/home
これで、example_userでFTPでログインすると、そのユーザの$HOME/path/to/homeが初期ディレクトリとなる。
ユーザごとの設定ファイルでは、local_rootだけでなく色々な設定が使えるらしい。
2011-10-07 (Fri) [長年日記]
■ [AWS] instance-storeのEC2をEBSブートのEC2に変換する
現在instance-storeで起動しているEC2のインスタンスを、EBSブートのEC2に変更することがあったので、その方法をまとめた。
ami-toolsのインストール
変換対象となるインスタンスに、ami-toolsをインストールする。
インスタンスのイメージファイルを作成する
現在動作しているEC2のバンドルボリュームを作成し、イメージに変換する。
# cd /tmp # ec2-bundle-vol -d /tmp -u YOUR_ACCOUNT_NUMBER # ec2-unbundle -m /tmp/image.manifest.xml -s /tmp/ -d /tmp/
EBSボリュームを用意する
EBSインスタンス用のEBSボリュームを作成し、アタッチする。
# ec2-create-volume --size 10 --availability-zone ap-southeast-1a # ec2-attach-volume vol-48f73422 --instance i-32dda733 -d /dev/sdf1
EBSボリュームにイメージを書き込む
作成したイメージを、ddを使ってEBSに書きこむ。データサイズに応じて、それなりに時間がかかるので注意。
# dd if=/tmp/image of=/dev/sdf1
EBSによるAMIの作成
まず、EBSのスナップショットを作成する。
# ec2-create-snapshot vol-48f73422
すこし時間がかかるので、作成されたのを待ってAMIを登録する。
# ec2-register --snapshot snap-2d2ccf46 --description="migrated image" --architecture i386 --root-device-name /dev/sda1 --name migrated image
これで作成されたAMIはEBSブートとなる。
2011-10-11 (Tue) [長年日記]
■ [Ruby][AWS] AWS SDK for Ruby 1.2.0 Release
いつの間にかでてた。リリースによると、ELBが新規にサポート対象となった。ELBはAWS Management Consoleではできないことが結構あるので、Rubyで使えるようになって嬉しい。
Management Consoleではできない事の例として、ひとつのEC2インスタンスを複数のELBに追加してみた。
AWS.config(YAML.load(File.read('config.yml'))) ec2 = AWS::EC2.new elb = AWS::ELB.new %w(my-lb1 my-lb2).each do |n| lb = elb.load_balancers.create(n, :availability_zones => ['us-east-1b', 'us-east-1c'], :listeners => [ {:port => 80, :protocol => :http, :instance_port => 80, :instance_protocol => :http} ]) lb.instances.register(ec2.instances.map {|i| i.id}) end elb.load_balancers.each do |lb| puts "#{lb.name}:" lb.instances.each do |i| puts "\t#{i.id}" end end
EC2インスタンスが既に起動していることを前提としている。ELBを二つ作成して、それぞれのELBにインスタンスを登録している。
今回は、EC2が二つ起動している状態で実行してみた。結果として、以下のように出力される。
my-lb1: i-4e624e2e i-9c614dfc my-lb2: i-9c614dfc i-4e624e2e
使い方は、ELBのドキュメントに。機能は比較的単純なので、簡単に使える。
2011-10-13 (Thu) [長年日記]
■ [Apache] 特定のディレクトリだけ認証を外す
Apacheで、サイト全体をBASIC認証などでアクセス制限を行っているときに、一部のディレクトリ以下のみ、認証の対象外にしたい場合には、以下のようにすれば良い。
たとえば、/var/www/default/html以下すべてがBASIC認証によって制限されていて、/var/www/default/html/noauth以下は認証なしにしたいような場合は、下記のように設定すればよい。
# 全体に対してBASIC認証の設定 <Directory /var/www/default/html> Options FollowSymLinks AllowOverride None Order allow,deny Allow from all AuthType Basic AuthName welcome AuthUserFile /var/www/default/.htpasswd require valid-user </Directory> # /var/www/default/html/noauth/ ディレクトリだけ認証の対象外にする <Directory /var/www/default/html/noauth> Order allow,deny Allow from all Satisfy Any </Directory>
Satisfyディレクティブは、ホストレベルの認証と、ユーザレベルの認証の組み合わせ方法を指定するディレクティブで、anyにすることで、ホストレベル認証とユーザレベル認証のどちらかの認証が通れば許可することができる。それとは逆に、Allに設定すると、それらすべての認証を通る必要がある(Satisfy Allがデフォルト)。
上記の設定だと、BASIC認証が設定されているが、ホストレベルの認証ですべてが許可されているので、BASIC認証による制限はかからないことになる。
■ [Linux][ssh] sftpでchrootする
これまではchrootが必要なケースではftpを使ってきていたのだが、opensshも4.9からchrootに対応しているということもあり試してみた。
今回は、特定のユーザのみchrootする設定。想定としては、Webサーバの公開領域のうち、特定のディレクトリ以下にのみアクセス可能なアカウントとする。具体的には、以下のような条件となる。
- ドキュメントルートは、/var/www/html
- chrootしたいディレクトリは、/var/www/html/path/to/upload
- upload_userというアカウントのみ、chrootするものとする
- upload_userは、sftpのみ使用可能とする
- その他のアカウントはchrootせずに、/var/www/html以下が読み書き可能
実際にやってみると、この条件を満たすのは意外と難しかった。
ユーザ作成
ユーザは普通に作れば良い。公開鍵認証を使いたいので、ssh-keygenで鍵を作っておく。
ディレクトリを作成
/var/www/html/path/to までディレクトリを作成する。この時、uploadディレクトリは作らない。 また、/var/www/chroot/upload までディレクトリを作成する。この時、/var/www/chrootまでは、所有者:グループをroot:root、パーミッションを755としておかなければならない。この条件があるため、通常のドキュメントルート以下にchrootするのが難しい。 uploadディレクトリは、upload_userや、その他のアカウントが読み書きできる所有者、グループ、パーミッションとする。
/var/www/html/path/to に、/var/www/chroot/uploadのシンボリックリンクを作成する。
sshd設定
/etc/ssh/sshd_configの設定を変更する。
まず、Subsystemを以下のように変更する。
Subsystem sftp internal-sftp
次に、下記の設定を追記する。
Match user upload_user ChrootDirectory /var/www/chroot X11Forwarding no AllowTcpForwarding no ForceCommand internal-sftp
このMatch userの設定で、upload_userのみ、/var/www/chrootにchrootするようになる。他の設定は、sftpのみ使えるようにするために必要なもので、chrootとは直接関係ない。
sshdを再起動して設定を反映させる。
これで、upload_userでsftpで接続すると、/var/www/chrootにchrootされた状態で接続できる。/var/www/chroot自体は、パーミッションの関係で書き込みができないが、その下にあるuploadディレクトリには書き込みができる。
また、他のアカウントについては、シンボリックリンクである/var/www/html/path/to/upload経由で読み書きができる。
これで、openssh 4.9以降の環境であれば、FTPを使う必要性がなくなったはず。
2011-10-17 (Mon) [長年日記]
■ [MySQL] 稼働中のMySQLに無停止でスレーブを追加する
MySQLは簡単にレプリケーションすることができて便利。最初からレプリケーションを前提に構築するのは簡単にできる。しかし、実は運用が始まってしまって、なかなか停止できないMySQLにも、ほぼ無停止でスレーブサーバを追加できる。現在の設定状況にもよるが、再起動は必要になるかも。
マスターでバイナリログを出力するようにする
my.cnfで、binlog_do_dbの設定をする。レプリケーション対象となるデータベースを指定する。
[mysqld] binlog_do_db=your_db
マスタのserver_idを設定する
my.cnf で server_idの設定をする。レプリケーションする際に、個々のサーバを識別するのに使われる。任意のユニークな整数を指定すれば良い。
[mysqld] server_id=10
ここまでの設定ができたら設定を反映させて、mysqlのデータディレクトリに、バイナリログが出力されていることを確認する。
マスターに、スレーブからマスターに接続するためのアカウントを作成する
レプリケーションするために、スレーブからマスターに接続する専用のアカウントを作成する。ここでは、replというユーザで作成している。
CREATE USER 'repl'@'10.%' IDENTIFIED BY 'password'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'10.%' IDENTIFIED BY 'password WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
マスターからデータをdumpする
$ mysqldump your_db --master-data --single-transaction > your_db.dump
mysqldumpに、--master-dataを付与するのがポイント。また、InnoDBであれば、--single-transactionを付与することで、ロックされることなくdumpを取得できる。MyISAMの場合は、--master-dataによって、自動的に--lock-all-tablesが有効になるので、すべてのテーブルがロックされてしまうので注意。また、データベース内にInnoDBとMyISAMのテーブルが混在している場合は、--single-transactionを付与すると、データの不整合が起こる可能性があるので、付与しないこと(すべてのテーブルがロックされる)。
スレーブのserver_idを設定する
マスターと同様に、server_idに任意の値を設定する。
[mysqld] server_id=20
スレーブに、マスターへ接続するためのアカウントを作成する
CHANGE MASTER TO MASTER_HOST='master_host_name',MASTER_USER='repl', MASTER_PASSWORD='password';
スレーブにdumpデータをロードする
スレーブにデータベースを作成して、dumpしたデータをロードする。
mysql> CREATE DATABASE your_db; mysql> \q $ mysql your_db < your_db.dump
スレーブにレプリケーションの設定をする
dumpしたファイルの先頭に、マスターのバイナリログのファイルとポジションが書かれているので、それを参照してCHANGE MASTERを実行する。
CHANGE MASTER TO MASTER_LOG_FILE='binlog_name', MASTER_LOG_POS=log_position;
マスターが正しく設定されているか確認
設定ができたら、SQLコマンドを実行して動作状況を確認する。
mysql> SHOW MASTER STATUS\G
スレーブでレプリケーションを開始
mysql> SLAVE START; mysql> SHOW SLAVE STATUS\G
動作確認
実際に、データを投入するなどしてから、SHOW MASTER STATUSでバイナリログのポジションに変化があるかどうか確認する。
mysql> SHOW MASTER STATUS\G
Slave_IO_Runningと、Slave_SQL_Runningが、両方共Yesになっていれば、レプリケーションは問題なく動作している。
2011-10-20 (Thu) [長年日記]
■ [AWS][Windows] Windows ServerのAMIを作成する際にはsidに注意
Amazon提供のAMIからWindows Serverを起動して、基本的な設定を作成した後にAMIを作成し、そこからインスタンスを増やす、といった場合には、sidに注意する必要がある。
単体で使用するときには問題にはならないと思うが、Active Directory環境で使用する際には、sidが重複しないようにする必要がある。sidが重複してしまうと、メンバーサーバに追加したアカウントの権限周りで、期待した動作をしなくなってしまう。
Amazon提供のAMIには、EC2ConfigService Settingというツールがあり、これを使うことでsidを変更することができる。
sidを変更するには、スタートメニューからEC2ConfigService Settingを起動して、[Bundle]タブから[Run Sysprep and Shutdown Now]ボタンを押す。すると、ボタンに書いてあるとおり、再起動ではなくシャットダウンが実行され、インスタンスが停止する。
この停止中にAMIを作成すると、Amazon提供のAMIの様に、インスタンスの起動時にsidが変更されるAMIができる。通常のバックアップ目的で作成するAMIなど、sidが変更されては困る場合は、この時にAMIを作ってはいけない。
AMIが必要なく、単にsidを変更したいのであれば、このままインスタンスを起動すれば良い。起動時にsidが変更される。
2011-10-24 (Mon) [長年日記]
■ [AWS][Ruby] Rubyからインスタンスを起動してELBにアタッチ
一時的にサーバを増やして既存のELBにアタッチする、という作業が今後何回か発生するようだったので、スクリプトを作成した。かなり粗削りだが、動くことは動く。
aゾーンにマスターサーバが一台いるので、bゾーンから始まって、一台増えるたびにaゾーンとbゾーンに交互にインスタンスを起動していく。すべてのインスタンスの起動が確認できたら、一気にELBに登録する。
require 'aws-sdk' number_of_slave = 3 instance_type = 't1.micro' ami_id = 'ami-deadbeaf' keypair_name = 'my-keypair' elb_name = 'my-elb' AWS.config(YAML.load(File.read('config.yml'))) ec2 = AWS::EC2.new azs = ec2.availability_zones.map(&:name).sort sgs = [ec2.security_groups['default'], ec2.security_groups['web']] instances = [] (1..number_of_slave).each do |n| az = azs[n % 2] i = ec2.instances.create( :image_id => ami_id, :instance_type => instance_type, :availability_zone => az, :security_groups => sgs, :key_name => keypair_name ) ec2.tags.create(i, "Name", :value => "slave-#{n}") instances << i end loop do if number_of_slave == instances.inject(0) {|sum, i| sum + (i.status == :running ? 1 : 0)} break end sleep 1 end elb = AWS::ELB.new lb = elb.load_balancers[elb_name] lb.availability_zones.enable('ap-northeast-1a', 'ap-northeast-1b') lb.instances.register(instances)
こういうルーチンワークは、どんどんスクリプト化していきたい。
2011-10-26 (Wed) [長年日記]
■ fuse 2.8.6はCentOS 5では動作しない
s3fsを使うためにfuseが必要なのだが、fuseのstable releaseの最新である2.8.6は、CentOS 5では動作しないことが分かった。mountしようとすると、次のようなエラーが出る。
/bin/mount: unrecognized option `--no-canonicalize'
これは、fuse 2.8.6で、mountコマンドに--no-canonicalizeオプションが必須になったかららしい。
CentOS 5に入っているmountコマンドはやや古く、--no-canonicalizeオプションがない。CentOS 6であれば対応している。--no-canonicalizeに対応していない古いmountを使っている環境の場合、fuse 2.8.5を使えば良い。
■ [AWS][Ruby] AWS SDK for Rubyでセキュリティグループの取得方法
ドキュメントによると、下記のように書いてある。
# get two existing security groups dbsvrs = ec2.security_groups['db-servers'] websvrs = ec2.security_groups['web-servers'] # allow instances in the 'web-servers' security group to connect # to instances in the 'db-servers' security group over tcp port 3306 dbsvrs.authorize_ingress(:tcp, 3306, websvrs)
AWS::EC2::SecurityGroupCollection#[]に、セキュリティグループ名を渡して取得している。実はこれは誤りで、例えば以下のようなコードを実行すると、エラーになる。
sg = AWS::EC2.new.security_groups['default'] sg.id #=> "default" sg.name #=> AWS::EC2::Errors::InvalidGroupId::Malformed: <?xml version="1.0" encoding="UTF-8"?><Response><Errors><Error><Code>InvalidGroupId.Malformed</Code><Message>Invalid id: "default" (expecting "sg-...")</Message></Error></Errors><RequestID>1950836d-dfea-4600-8ad0-b852cc056b2a</RequestID></Response>
エラーメッセージにあるように、AWS::EC2::SecurityGroupCollection#[]には、idを渡さなけれればならない。
sg = ec2.security_groups['sg-98a62799']
また、名前で取得するには、下記のようにfilterを使う。
sg = ec2.security_groups.filter('group-name', 'default').first
ドキュメントは誤りがあったということで、次回のリリース時に訂正される予定。
しかし不思議なことに、下記のコードは正しく動作する。
ec2 = AWS::EC2.new i = ec2.instances.create(:image_id => 'ami-dcfa4edd', :instance_type => 't1.micro', :security_groups => ec2.security_groups['default']) i.security_groups.each {|sg| puts sg.id} #=> sg-98a62799
どうやら、AWS::EC2::InstanceCollection#createは、オプションとして受け取るセキュリティグループは、名前かIDのどちらでも受け付けるかららしい。これはちょっと混乱する…。
2011-10-28 (Fri) [長年日記]
■ [AWS] Juniper SRX100でAmazon VPCへVPN接続
Amazon VPCに対応しているJUNOSが動作しているSRX100で、VPN接続を試してみた。
cloudpackブログ - VPCとSSG5を接続にあるように、最後の設定ファイルのダウンロードまでは同じように設定。
SSG5では、設定ファイルのマージができるのだが、SRX100(JUNOS 10.4)では、どうやらマージはできないようだったので、sshで接続をして、cliでダウンロードしてきたコマンドを実行することにした。
最初は、SSG5と同様に全ての設定をそのまま適用すればよいと思ったのだが、それだとうまく繋がらなかった。そこで、よく設定ファイルを見返してみたところ、現状の設定に合わせて調整が必要な部分があった。
set security ike gateway gw-vpn-d8ec46d9-1 external-interface ge-0/0/0.0
ここでは、external-intecfaceとして、ge-0/0/0.0というインターフェースが指定されているが、試した機器ではexternalはfe-0/0/0だったので、そちらに修正。上記はtunnel.1の設定で、これと同様にtunnel.2も設定する必要がある。
それらの設定を変更したところ、無事にVPN接続できるようになった。
2011-10-31 (Mon) [長年日記]
■ [AWS][Ruby] AWS SDK for Ruby 1.2.2 Release
AWS SDK for Rubyの1.2.2がリリースされた。
今回は、小粒な修正。
まず、SimpleDBで複数のドメイン名を同時に扱うことができるようになった。これは、フォーラムにて機能提案があったもの。githubからpull requestがあって、それが反映されたものらしい。
また、自分が以前に書いたドキュメントに誤りがあった件が訂正されていた。単なるドキュメントの訂正が、ソフトウェアのリリースに含まれるのは、どうやらソースコードに含まれるコメントからドキュメントが生成されているから。
いずれにしても、フォーラムでのフィードバックによる修正のみのリリース。最近のマイナーアップデートはこういうパターンが多い。ユーザから要望が比較的早期に反映されるのは、ユーザとしてはとても頼もしく感じる。
■ [AWS][Ruby] ELBにインスタンスをアタッチする
ちょっと前に、Rubyからインスタンスを起動してELBにアタッチするコードを書いた。今回は、すでに起動しているインスタンスをELBにアタッチするだけのコード。
今のところ、AWS Management Consoleで一つのインスタンスを複数のELBに対してのアタッチができないので作成した。
require 'aws-sdk' require 'optparse' account = '' elb_name = '' instance_id = '' opt = OptionParser.new opt.on('-a VAL', '--account VAL') {|v| account = v} opt.on('-e VAL', '--elb-id VAL') {|v| elb_name = v} opt.on('-i VAL', '--instance-id VAL') {|v| instance_id = v.split(',')} opt.parse!(ARGV) config = YAML.load(File.read('config.yml')) AWS.config(config[account]) ec2 = AWS::EC2.new found_instances = [] not_found_instance = [] instance_id.each do |id| i = ec2.instances[id] if i.exists? found_instances << i else not_found_instance << i end end if not_found_instance.size > 0 puts "Instances not found: #{not_found_instance.map(&:id)}" end unless found_instances.size > 0 puts "Attachable instances not found." exit 1 end elb = AWS::ELB.new lb = elb.load_balancers[elb_name] unless lb.exists? puts "ELB not found: #{elb_name}" exit 2 end lb.instances.register(found_instances) puts "attached:" lb.instances.each do |i| puts "\t#{i.id}" end
以下のように実行する。インスタンスIDは、カンマ区切りで複数指定可能。
$ ./attach_elb.rb -a myaccount -i i-abcd1234,i-5678efab -e my-elb
前回は、インスタンスを自分で起動していたため、インスタンスの存在をいちいちチェックしていなかったが、今回はスクリプトの起動時にパラメータとして渡されたインスタンスIDをチェックするようにしている。ELBも同様にチェックしている。
■ [AWS] Amazon VPCで複数拠点接続
複数拠点でVPN接続をしてみたところ、VPNはつながるようになったのだが、AWSと通信ができない問題が起こった。VPNにはつながっているし、ルーティングも問題ないように見えた。セキュリティグループや、ルータのファイアウォールの問題でもない。
色々と試しても解決しなかったので、AWSのサポートに問い合わせたところ、複数拠点接続を行う際に、設定が必要ということで、ドキュメントと、参考になる記事としてAmazon VPCで複数拠点間接続を試してみた - log4motoをご教示いただいた。
つまり、接続するロケーションごとに、BGPの経路情報を設定する必要があるとのこと。これまで、BGPは名前は聞いたことがあるぐらいで扱ったことがなかったので、ここに問題があるということが全然分からなかった。
今回は、192.168.254.0/24と、192.168.1.0/24という、二つのレンジを持つネットワークが二つ接続されていたので、それぞれVPN接続をしているルータに、設定をした。
今回は対象がScreen OSなので、AWSからダウンロードできる設定は下記のようになっていた。
set ipv4 network 0.0.0.0/0
これを、192.168.254.0/24のネットワークの場合は、下記のように変更した。
set ipv4 network 192.168.254.0/24
これで、正しく経路情報が設定され、VPN経由でAWSに接続することができるようになった。