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

このWIKIを編集するにはパスワード入力が必要です

認証パスワード