読者です 読者をやめる 読者になる 読者になる

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 を消すことは困難