capistrano/capistranoでサーバー管理/SSH接続環境の整備
SSH接続環境の整備
なぜSSH接続環境の整備をするのか?
capistranoに関する記述の多くは、root権限でのパスワードログイン、もしくはパスフレーズ無しSSH鍵を使用しての自動ログインが前提になっています。
しかし、実際にはそんな形で運用している本番サーバーは無いはずです。(無いですよね?)
本番環境ではない開発環境なら問題無いでしょうが、ここではサーバー管理がテーマなのでそのあたりは細かくやっていこうと思います。
rootでのログインについて
capistranoの性質上、何らかの形でrootでのログインが認められないと運用がめんどくさすぎます。
root権限でのログインを認めないとなるとsudoということになるのでしょうが、sudoはユーザーのパスワードが分かっただけでroot権限でのコマンド実行ができてしまいますし、実行するコマンドを制限したとしてもいちいちそれを解除するための運用が発生してしまいます。
そこで、ある程度のセキュリティを保ちつつ利便性も得るやり方として、パスフレーズ有りのSSH鍵+SSHアクセス元の制限という手法を提案します。
SSH鍵パスフレーズログイン
せっかくcapistranoを使っているので、作業は全て管理サーバーで行います。
rootユーザーでSSH鍵を生成します。
# ssh-keygen -t rsa
パスフレーズは任意の物を入力して下さい。
公開鍵を登録したauthorized_keysをcapistranoのプロジェクトディレクトリに置きます。
実際の所置き場所はどこでも良くてプロジェクトディレクトリである必要も無いですが、便宜的にこうしておきます。
# mkdir -p /opt/capistrano/manage/files/root/.ssh # cat /root/.ssh/id_rsa.pub > /opt/capistrano/manage/files/root/.ssh/authorized_keys
作成したファイルをアップロードするタスクを作成します。
初期設定で作ったconfig/deploy/manage.rbを以下の内容にします。
load './config/servers.rb' task :upload_keys do src = 'files/root/.ssh/authorized_keys' dir = '/root/.ssh' dst = '/root/.ssh/authorized_keys' on roles( :all ) do execute "mkdir -p #{dir}" upload! src, dst, :recursive => true execute "chmod 700 #{dir}" execute "chmod 600 #{dst}" execute "chown -R root:root #{dir}" end end
ファイルをアップロードしてパーミッションの設定をするだけのタスクです。
以前と違うのは #{dir} といった中括弧を使った箇所と upload! というメソッドがあることでしょうか。
#{}に関しては、ダブルクォートで囲まれた箇所において、rubyのコードを実行した結果を返すための記述です。
dirには /root/.ssh という文字列が格納されているので、その結果が返されてその部分に /root/.ssh と記述されているのと同じ動きになります。
upload! メソッドはそのまんま、ファイルを対象サーバーにアップロードするメソッドです。
第一引数にローカルのソースファイルパス、第二引数にリモートのファイルパスです。
第三引数以降は対応するシンボルでオプションを指定しますが、ここでは詳しく書きません。
詳しく知りたい場合はnet/scpのuploadメソッドあたりを見て下さい。
では、実行しましょう。
# cap manage upload_keys
特にエラーが出ていなければ完了です。
実際にパスフレーズのみでサーバーにログインできるか試してみましょう。
# ssh web1
パスワードではなく、パスフレーズの入力でログインできるようなら成功です。
パスワードを聞かれるようなら、エラーが出ずにファイルがアップロードされているか、パーミッションなどがちゃんと設定されているか確認してみて下さい。
ただし実はこの時点で、capistranoのタスクもパスフレーズの設定無しでは正常に実行できない状態になっています。
そこで、config/servers.rbを以下の様な形に変更してしまいます。
SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { :user => 'root', :passphrase => 'passphrase', } end server 'manager', :roles => [ :manager ] server 'web1', :roles => [ :web ] server 'web2', :roles => [ :web ] server 'app1', :roles => [ :app ] server 'app2', :roles => [ :app ] server 'db1', :roles => [ :db ] server 'db2', :roles => [ :db ]
hosts設定が無い場合は適宜内容を読み替えて下さい。
ユーザーの設定は上部の所で行うため、server定義の所からは削除します。
また、今後はパスフレーズを使うため、パスワードの設定は必要無いので削除します。
パスフレーズの設定はユーザーの設定と同じ箇所で設定します。
上部のSSHKit::Backend::Netssh.configureで始まる箇所に関しては説明すると長くなる上、今後いじることはあまり無い所なので詳しいことは省きます。
詳しいことが知りたい場合はcapistrano/sshkitのEXAMPLESや、NetSSHのドキュメントなどを見て下さい。
さて、これでcapistranoのタスクが正常に動作するようになったはずですので、consoleを試してみます。
# cap manage console
正常にconsole機能が使えればパスフレーズログインは成功です。
パスフレーズを都度入力にする
パスフレーズがソース中に書かれているのはセキュリティ上あまり良くなさそうですので、タスク実行時に入力する形にします。
オプション設定の箇所を以下の様にします。
set :pass, ask( 'Passphrase?: ', nil, :echo => false ) SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { :user => 'root', :passphrase => fetch( :pass ), } end
ではコンソールで動作確認してみましょう。
# cap manage console
パスフレーズの入力と、コマンド実行が正常にできたでしょうか?
askメソッド及びfetchメソッドを使用すると、インタラクティブな入力が可能になり、:echo => false が無ければ入力した値が見えるようになります。
なお、少なくともCentOS7の環境でパスワードの入力を誤るとコンソールが異常動作するので、パスワードは間違えないようにしましょう。
SSHキーアップロード用のタスクを分ける
この状態だとauthorized_keysのアップロードが済んでいないサーバーに対してのタスクが実行できません。
つまり、新たにサーバーを追加した場合にはそのまま素直に対応ができないということになります。
そこでSSHキーのアップロードを行うタスクを外に出し、そちらではパスワード認証を使ってタスク実行するようにします。
まず、config/servers.rb ではサーバーのリストのみを記述します。
server 'manager', :roles => [ :manager ] server 'web1', :roles => [ :web ] server 'web2', :roles => [ :web ] server 'app1', :roles => [ :app ] server 'app2', :roles => [ :app ] server 'db1', :roles => [ :db ] server 'db2', :roles => [ :db ]
そして config/sshkey.rb を作成し、パスフレーズを入力する形の記述をします。
set :pass, ask( 'Passphrase?: ', nil, :echo => false ) SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { :user => 'root', :passphrase => fetch( :pass ), } end
もう一つ config/password.rb を作成し、こちらはパスワードを入力する箇所のみ記述します。
SSHキーを使わず、強制的にパスワード認証にするような記述も行います。
set :pass, ask( 'Password?: ', nil, :echo => false ) SSHKit::Backend::Netssh.configure do |ssh| ssh.ssh_options = { :user => 'root', :auth_methods => [ 'password' ], :password => fetch( :pass ), } end
この状態で、アップロードタスクは config/deploy/sshkey.rb ファイルを作って外に出します。
load './config/password.rb' load './config/servers.rb' task :upload do src = 'files/root/.ssh/authorized_keys' dir = '/root/.ssh' dst = '/root/.ssh/authorized_keys' on roles( :all ) do execute "mkdir -p #{dir}" upload! src, dst, :recursive => true execute "chmod 700 #{dir}" execute "chmod 600 #{dst}" execute "chown -R root:root #{dir}" end end
最初の load の部分ではパスワード入力の方を読み込むのがポイントです。
config/deploy/manage.rb の方はSSHキーの処理を読み込みます。
load './config/sshkey.rb' load './config/servers.rb'
これで、初回のSSHキーアップロード時には以下のタスクを実行するとパスワード入力で処理が実行できます。
# cap sshkey upload
オプションに --hosts もしくは --roles を使うと対象を指定して実行できますので、servers.rbにサーバーを追加したらそのサーバーのみ実行しましょう。
# cap --hosts web3 sshkey upload
hostsファイルを編集していない場合はロールとして設定してあるはずなので --roles で指定します。
# cap --roles web3 sshkey upload
IPが分かるなら --hosts でも良いです。
# cap --hosts 192.168.0.13 sshkey upload
カンマ区切りで複数指定することもできます。
# cap --hosts 192.168.0.13,192.168.0.14 sshkey upload
SSH用ユーザー作成
大抵は作るはずですので、ここらでSSH接続用の一般ユーザーを作成しておきます。
下記のタスクをconfig/deploy/manage.rbに追加します。
task :useradd do user = 'user1' password = 'password' salt = rand( 36**8 ).to_s( 36 ) shadow_hash = password.crypt( '$6$' + salt ) on roles( :all ) do execute "id #{user} 2>/dev/null | | useradd -p '#{shadow_hash}' #{user}" end end
説明は不要かと思いますが念の為、2行目で追加するユーザー名、3行目でパスワードを記述してありますので、適宜書き換えて下さい。
もしファイル内に素のパスワードを記述したくない場合は、まず予め以下のコマンドでパスワードのハッシュ値を生成しておいて下さい。
# ruby -e 'p "password".crypt("$6$"+rand(36**8).to_s(36))' "$6$jjv9aj6r$n5G140acNo4H9.MuZ9gRlNOXJXZCd2ODykiLkt7E101zz6e.xnrULl0DyUdEem3P7CefxzLu8LR4ImLA/0ATa."
"password"の部分がパスワードですので、パスワードがhogehogeの場合は "hogehoge".crypt~ などとして下さい。
そしてタスクの方はこのようにします。
task :useradd do user = 'user1' shadow_hash = '$6$jjv9aj6r$n5G140acNo4H9.MuZ9gRlNOXJXZCd2ODykiLkt7E101zz6e.xnrULl0DyUdEem3P7CefxzLu8LR4ImLA/0ATa.' on roles( :all ) do execute "id #{user} 2>/dev/null | | useradd -p '#{shadow_hash}' #{user}" end end
ユーザー名やパスワードをインタラクティブに入力する場合はタスクを以下の様にします。
task :useradd do set :user, ask( "Username?: ", nil ) set :password, ask( "Password?: ", nil, :echo => false ) salt = rand( 36**8 ).to_s( 36 ) user = fetch( :user ) shadow_hash = fetch( :password ).crypt( '$6$' + salt ) on roles( :all ) do execute "id #{user} 2>/dev/null | | useradd -p '#{shadow_hash}' #{user}" end end
タスクの実行は下記の通りです。
# cap manage useradd
SSHアクセス制限
SSHのアクセスを制限するにはいくつか方法がありますが、とりあえず設定箇所を1つにまとめるため、ここではsshd_configを使用することにします。
設定する内容は、 PermitRootLogin を without-password にしてrootでのパスワード認証を無効にすること。
PermitRootLogin without-password
それと、rootユーザーのアクセス元IPアドレスをmanager(192.168.0.1)からのみにすることです。
AllowUsers root@192.168.0.1
もしroot以外でのログインを行う可能性がある場合は、そのユーザーも記述する必要があります。
AllowUsers root@192.168.0.1 user1 user2
では実際の作業に移ります。
sshd_config自体を作成してアップロードするタスクを作っても良いですが、同じことをやってもつまらないのでシェルスクリプトを作ってリモートで実行する形にしましょう。
下記のシェルスクリプトをfiles/tmp/sshd_config.shとして保存します。
#!/bin/sh SSHD_CONFIG="/etc/ssh/sshd_config" RELOAD="systemctl reload sshd" ROOT_LOGIN="without-password" ALLOW_USERS="root@192.168.0.1 user1" sed -i "s/^#\?PermitRootLogin .*/PermitRootLogin ${ROOT_LOGIN}/g" ${SSHD_CONFIG} grep -q AllowUsers ${SSHD_CONFIG} if [ $? -eq 0 ]; then sed -i "s/^#\?AllowUsers .*/AllowUsers ${ALLOW_USERS}/g" ${SSHD_CONFIG} else echo "AllowUsers ${ALLOW_USERS}" >> ${SSHD_CONFIG} fi ${RELOAD} exit
ユーザーの設定は8行目を編集して使って下さい。
sshdのリロードはOSの仕様に合わせて5行目を変更して下さい。
if文にしても良いかもしれませんが。
これをリモートで実行するタスクを作成します。
config/deploy/manage.rbに以下のタスクを追加します。
task :sshd_config do src = 'files/tmp/sshd_config.sh' dst = '/tmp/sshd_config.sh' on roles( :all ) do upload! src, dst execute "chmod 755 #{dst}" execute "#{dst}" execute "rm -f #{dst}" end end
タスク自体は思ったよりも新しくないですね・・・
今回はsshdの設定なので、ミスがあると接続できなくなってしまいます。
危険極まりないのでまず1台で実行して問題無いのを確認してから全体に適用しましょう。
# cap --hosts web1 manage sshd_config
タスクを実行したらパスフレーズログインできるか確認します。
# ssh 192.168.0.11
consoleも確認しておきます。
# cap --hosts 192.168.0.11 manage console
問題無ければ全サーバーに適用しましょう。
# cap manage sshd_config
念の為consoleも確認します。
# cap manage console
ユーザーを追加した場合はスクリプトを修正して再度タスクを実行するのを忘れないで下さい。
SSH接続環境の整備はこのぐらいで終わりにします。
より高いセキュリティと利便性を両立できるやり方がある場合はぜひ教えて下さい。
- 最終更新:2017-01-18 10:31:54