Goal
- sshをプログラマブルにやる
- 踏み台サーバ経由でsshする
- 秘密鍵をメモリ上におき、ディスクにおかなくていいようにする
- (おまけ)最後にspecinfra, itamaeをこの方式でつかえるようにする
Motivation
秘密鍵の管理がややこしい。案件が増えるにつれてどんどん増えていく。踏み台サーバの鍵と目的サーバの鍵が異なることもあり、サーバ構築時(ユーザ作成前)と構築後でつかう鍵が異なることもある。鍵を担当者に共有するのもややこしい。
少なくとも自分でコントロールできる案件については、構築に関係する鍵をプログラマブルに管理し、構築自体動的にやりたいと思うようになった。
Mechanism
key_data
ふつうコマンドラインのオプションでは-i
など、鍵のパスを指定するのが一般的だが、Net::SSH
には key_data
というオプションで、文字列で直接秘密鍵を指定する仕組みがある。 1 これをつかえば、秘密鍵をディスク上に保存することなくsshできる。
require 'net/ssh'
key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxx.....
-----END RSA PRIVATE KEY-----
EOS
Net::SSH.start('x.x.x.x', 'ec2-user',
port: 22,
key_data: key_data
) do |ssh|
ssh.exec!("hostname")
end
# => "ip-y-y-y-y\n"
踏み台サーバごしに key_data
をつかえれば今回の要件をクリアできるという寸法。
ProxyCommand
capistranoで踏み台経由のデプロイするときは、今までProxyCommandをつかっていた。capistranoは裏でNet::SSHをつかってるので、Net::SSH::Proxy::Command
をつかってプロキシできる。
今回の多段sshも、はじめこれと同じ要領でできるような気がしていたが、どうもできない。
net-ssh/command.rb at v5.2.0 · net-ssh/net-ssh · GitHub
Net::SSH::Proxy::Command
は引数に渡されたコマンドを、OSコマンドライン上で実行しているにすぎない(IO.popen
)。つまり Net::SSH
がもっているプログラマブルな鍵管理の仕組み(key_data
など)を適用できない。しかしコマンドラインのsshでは秘密鍵を直接文字列で渡す仕組みはない。(コマンドライン実行されたものは履歴に残るので仮にできたとしてもやるべきではない)
require 'net/ssh'
require 'net/ssh/proxy/command'
key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxx.....
-----END RSA PRIVATE KEY-----
EOS
Net::SSH.start('x.x.x.x', 'ec2-user',
port: 22,
key_data: key_data,
# ↓のコマンドに踏み台サーバログインのための鍵のパスを指定する必要がある
# 踏み台へのアクセス時にはkey_dataに指定した鍵はつかわれない
proxy: Net::SSH::Proxy::Command.new('ssh -W %h:%p metheglin@a.a.a.a -i ~/.ssh/id_rsa'),
) do |ssh|
ssh.exec!("hostname")
end
# => "ip-y-y-y-y\n"
つまり、ProxyCommandつかうなら必ず秘密鍵がファイルとしてディスク上に配置されていなければいけない。今回の要件ではProxyCommandはつかえないことになる。
net-ssh-gateway
じつはわたしは、多段sshについて理解がとぼしく、軽く混乱状態にあった。ProxyCommandができないとわかると混乱きわまってあきらめかけた。
一度冷静になって、できないわけないと思い直し、歯を食いしばって調べ直した結果以下のgemを見つけた。最近歯医者に行ったからくいしばり力があがってたのかもしれない。
READMEにはactiveに開発されてないとコメントがあるが、中身はすごいシンプルで、214行くらいしかない。2 このgemは、シンプルにローカルフォワード3をおこなっている。適当なポートがはらいだされて4、それがフォワードの窓口となる。プログラマはそのポートについて意識しなくてもいい。
Sample Code
net-ssh-gatewayをつかって、秘密鍵をディスクにおかない多段sshは以下のようにできる。
gem install net-ssh-gateway
require 'net/ssh/gateway'
key_data = <<-EOS
-----BEGIN RSA PRIVATE KEY-----
MIIEowxxxxxxxxxxxx...
-----END RSA PRIVATE KEY-----
EOS
gateway = Net::SSH::Gateway.new('x.x.x.x', 'ec2-user', key_data: key_data)
ssh = gateway.ssh('a.a.a.a', 'ec2-user',
key_data: key_data
)
ssh.exec!("hostname; pwd; id")
ssh.close
gateway.close(ssh.transport.port)
Net::SSH::Gateway.new
した時点で、踏み台への接続が確立される。
その後 gateway.ssh()
するともういっこ接続が確立される。
ここでは踏み台サーバとターゲットサーバで同じkey_data
をつかっているが、別の鍵にすることも可能。
specinfra
specinfraでこのgatewayごしのssh接続をどうつかうか?
specinfraではハックポイントが用意されていて、カスタムのssh接続をつかえるようになっていた。5
上記のコード gateway.ssh()
でつくった接続 ssh
を以下のように指定すればOK。
specinfra = Specinfra::Backend::Ssh.new(
request_pty: true,
disable_sudo: false,
ssh: ssh,
ssh_options: {
user: 'ec2-user',
}
)
specinfra.run_command("hostname; pwd; id")
# ssh.close
# gateway.close(ssh.transport.port)
itamae
itamaeはコマンドライン上の実行を主に想定してるっぽいので、大胆なハックができない。
以下のようにプログラマブルで実行用のRunnerをつくることでしのぐ。
上記specinfraのコードで作成した変数specinfra
を指定してつかう。
recipe = File.expand_path("recipes/ping.rb", __dir__)
Itamae::InlineRunner.run(
[recipe],
specinfra: specinfra,
node: {test: 1234},
)
module Itamae
class InlineRunner < Itamae::Runner
class << self
def run(recipe_files, options)
Itamae.logger.info "Starting Itamae... #{options[:dry_run] ? '(dry-run)' : ''}"
backend = Backend::SshInline.new(options)
runner = self.new(backend, options)
runner.load_recipes(recipe_files)
runner.run
runner
end
end
private
def create_node
return @options[:node] if @options[:node]
super
end
end
module Backend
class SshInline < Base
def initialize(options)
@options = options
@backend = options[:specinfra]
@executed_commands = []
end
def disable_sudo?
!@options[:sudo]
end
end
end
end
Environment
% ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
[2] pry(main)> Net::SSH::Version::STRING
=> "5.2.0"
[3] pry(main)> Net::SSH::Gateway::VERSION
=> "2.0.0"
[4] pry(main)> Specinfra::VERSION
=> "2.82.4"
[5] pry(main)> Itamae::VERSION
=> "1.10.6"
-
net-ssh-gateway/gateway.rb at v1.2.0 · net-ssh/net-ssh-gateway · GitHub ↩
-
net-ssh-gateway/gateway.rb at v1.2.0 · net-ssh/net-ssh-gateway · GitHub (自動的にポートの払い出しがおこなわれるが、番号を明示指定することもできる。) ↩