LGTM

Looks Good To Me

rancid の.cloginrc をvault 管理にする

rancid というツールがある.ネットワーク運用するにあたって,もはや手放せなくなっている.rancid のなにが便利かについては別エントリで書こうと思うが,本来の目的よりも付随する xlogin スクリプト (clogin, jlogin, ...) を気に入って使っている.

xlogin について簡単に言えば

  1. ネットワークデバイスへのログインを自動化する
  2. 指定したコマンド群を逐次実行し,標準出力に出す
  3. スクリプティングによって実行コマンドを制御する

ようなやつで,1. のための認証情報を~/.cloginrc に書いておく必要がある.パスワード入力が不要になるので「10 台のルーターshow interfaces descriptionsshow bgp summaryshow ospf neighborを打ってgrep に渡す」とか「100 台のルーターでsyslog の設定を変える」みたいなことが簡単にできる.

問題意識

ネットワークデバイスにログインするために 共有のジャンプサーバーを用意することが多く,xlogin をつかうには そこに~/.cloginrc を置く必要がある.

「共有サーバーに ファイルとして認証情報(id + password) を置く」というのがイマイチだなあと思っていた.*1 というのは,

そこでvault を手元のローカルホストで動かしておいて,.cloginrc の情報をそこに格納しておくと ずいぶんマシになった.

  • super-user に読まれてしまうのは変わらない
  • 認証情報の実体がローカルホストにあり,暗号化される.ssh セッションを切れば認証情報にアクセスできなくなる
  • 認証情報へのアクセスログが残るので,不正アクセスに気づける

vault の設定

セットアップ (ローカルホスト)

プラットフォームにあったvault をダウンロード し,PATH のどこかに置く.

設定ファイルを書く.

# ~/.vault

backend "file" {
  path = "/Users/codeout/.vault.d"
}

listener "tcp" {
  address = "localhost:8200"
  tls_disable = 1  # ssh tunnel を通すのでTLS 不要
}

vault server を起動し,初期化する.

$ vault server -config ~/.vault >&/dev/null &
$ export VAULT_ADDR=http://localhost:8200

$ vault init -key-threshold 1 -key-shares 1
vault $ vault init -key-threshold 1 -key-shares 1
Key 1: ced979c0b1c0ff62b2ac2aa08e54b8258bd4d88170ed67627b4c0fc3dd87133c
Initial Root Token: b5aae184-0fcf-3f7d-09db-9928d82eca56

Vault initialized with 1 keys and a key threshold of 1. Please
securely distribute the above keys. When the Vault is re-sealed,
restarted, or stopped, you must provide at least 1 of these keys
to unseal it again.

Vault does not store the master key. Without at least 1 keys,
your Vault will remain permanently sealed.

ローカルホストでの利用なので master key は1つにした.

デフォルト設定ファイルの評価タイミングが雑っぽくて,今日時点では-config ~/.vaultexport VAULT_ADDR=http://localhost:8200 を省略することができない.そのうち~/.vault がきちんと評価されるようになって,省略可能になる気がする.

起動直後はsealed なので unseal しておく.

$ vault unseal ced979c0b1c0ff62b2ac2aa08e54b8258bd4d88170ed67627b4c0fc3dd87133c
Sealed: false
Key Shares: 1
Key Threshold: 1
Unseal Progress: 0

ログの設定.

$ mkdir ~/.vault.d/log

vault $ vault audit-enable file path=/Users/codeout/.vault.d/log/vault_audit.log
Successfully enabled audit backend 'file'!

.cloginrc をvault に格納する.組織ごとに異なる.cloginrc を1つのvault に入れる必要があるので,key を分ける.ついでにpolicy も分けておいて 必要最小限のデータにのみアクセスを許可する.

チームA の.cloginrcsecret/team-a に格納する例:

$ vault auth b5aae184-0fcf-3f7d-09db-9928d82eca56
Successfully authenticated!
token: b5aae184-0fcf-3f7d-09db-9928d82eca56
token_duration: 0
token_policies: [root]
# ./team-a.hcl

path "sys/*" {
  policy = "deny"
}

path "secret/*" {
  policy = "deny"
}

path "secret/team-a" {
  policy = "write"
}

path "auth/token/lookup-self" {
  policy = "read"
}
$ vault policy-write team-a team-a.hcl
Policy 'team-a' written.

$ vault write secret/team-a cloginrc=@/Users/codeout/.cloginrc
Success! Data written to: secret/team-a

最後に,ポートフォワードと環境変数を転送するよう~/.ssh/config を設定しておく.

# ~/.ssh/config

Host            jump-server
  Hostname      192.168.3.102
  RemoteForward 8200 localhost:8200
  SendEnv       VAULT_TOKEN
確認

5分で失効するトークンを発行して ローカルホストから値が取れることを確認する.

$ vault token-create -lease=5m -policy=team-a
Key             Value
token           ccc70554-da81-4d2f-08be-d0d38232395c
token_duration  300
token_renewable true
token_policies  [team-a]

$ VAULT_TOKEN=ccc70554-da81-4d2f-08be-d0d38232395c vault read secret/team-a
Key             Value
lease_duration  2592000
cloginrc        ### default
add method    *   {ssh} {telnet}
add user      *   codeout
add password  *   {some!secret} {some!secret}

add method    192.168.0.79  {telnet}
add password  192.168.0.79  {another!secret} {another!secret}

ログが残ることも確認.

{"time":"2015-11-28T15:29:11Z","type":"request","auth":{"display_name":"token","policies":["team-a"],"metadata":null},"request":{"operation":"read","path":"secret/team-a","data":null,"remote_address":"127.0.0.1"},"error":""}
{"time":"2015-11-28T15:29:11Z","type":"response","error":"","auth":{"display_name":"","policies":["team-a"],"metadata":null},"request":{"operation":"read","path":"secret/team-a","data":null,"remote_address":"127.0.0.1"},"response":{"secret":{"lease_id":""},"data":{"cloginrc":"hmac-sha256:46ac8a23ac9efff3c42e5b9e31a7854e50f4a4e713810388003dbfcb1c9a607b"},"redirect":""}}

5分後には値が取れなくなっているはず.

セットアップ (ジャンプサーバー)

こちらも プラットフォームにあったvault をダウンロードし,PATH のどこかに置く.

.zshrc などにvault のURL を設定する.外部からジャンプサーバーにssh するときに張るssh tunnel を指定しておく.

# ~/.zshrc

export VAULT_ADDR=http://localhost:8200

rancid がインストールされているものとして,.cloginrc を書き換える.

.cloginrc はtcl スクリプトとして読まれるため,必要に応じてvault から認証情報を取得する動作になる.

# ~/.cloginrc

if [ catch {eval [eval exec "vault read -field=cloginrc secret/team-a"]} reason ] {
    send_user "\nError: $reason\n"
    exit 1
}

可能であれば,ssh クライアントから環境変数VAULT_TOKEN を受け付けるよう sshd を設定する.

# /etc/ssh/sshd_config

AcceptEnv LANG LC_* VAULT_TOKEN

つかいかた

ローカルホストでvault を起動し,トークンを発行する.-lease を指定しなければ30日有効なトークンが発行されるが,期間は適宜.

$ vault server -config ~/.vault >&/dev/null&
$ vault unseal ced979c0b1c0ff62b2ac2aa08e54b8258bd4d88170ed67627b4c0fc3dd87133c
$ vault auth b5aae184-0fcf-3f7d-09db-9928d82eca56

# 普段は上記の状態で動かしっぱなし

$ vault token-create -lease=24h -policy=team-a
Key             Value
token           d78015ad-e903-2122-dea9-6b1bcb970391
token_duration  86400
token_renewable true
token_policies  [team-a]

トークンを渡しつつ,ジャンプサーバーにssh

$ VAULT_TOKEN=d78015ad-e903-2122-dea9-6b1bcb970391 ssh jump-server

これでxlogin できるようになっているはず.

$ jlogin 192.168.3.103
192.168.3.103
spawn ssh -c 3des -x -l codeout 192.168.3.103
Password:
--- JUNOS 12.1X46-D10 built 2013-12-05 15:04:51 UTC
codeout@vsrx>

ローカルホストでvault がunsealed で動いており,ssh tunnel 経由でジャンプサーバーからvault にアクセスでき,トークンがある場合のみ認証情報にアクセスできるようになった. ローカルホストの~/.vault.d/log/vault_audit.log には認証情報へのアクセスがすべて記録されるので,不正アクセスの調査ができる.

別のチームB のジャンプサーバーにssh する場合は,vault に格納するkey, policy 名を切り替えればよい.

ジャンプサーバーのsshd を設定変更できない場合は クライアントから環境変数を渡せないので,ログイン後にexport VAULT_TOKEN=d78015ad-e903-2122-dea9-6b1bcb970391 する. このとき,一応コマンドヒストリーに残らないよう気をつける.たとえばzshhist_ignore_space が設定されている場合は,export の先頭に半角スペースを入れておけばヒストリーに残らない.

どうせsuper-user には/proc/<pid>/environps e などで環境変数を読まれてしまうが,いちおう余計なところに残さないほうがいい.

まとめ

.cloginrc をまるまるvault 管理にすることで,より安全にxlogin を使えるようになった.やはりsuper-user は認証情報にアクセスできてしまうため 完全ではないが

  • 認証情報の実体がローカルホストにあり,暗号化される.ssh セッションを切れば認証情報にアクセスできなくなる
  • 認証情報へのアクセスログが残るので,不正アクセスに気づける

構成を実現できた.

環境変数をsuper-user から隠す方法 もしくはうまく暗号化する方法があれば完璧なので,知見をお持ちのかたはぜひ教えてください.

*1:ssh + pubkey-auth + ssh-agent にすれば,そもそも~/.cloginrc に書かなくてよいのでは? という意見もあって,そのとおりなんだけど,super-user はssh-agent のsock-file を読めるので一緒だし,pubkey-auth 非対応のデバイスもあるし,enable password のようなplaintext なcredential も扱わないといけないので,~/.cloginrc を消すことは困難