LGTM

Looks Good To Me

netconf / restconf 時代にもxlogin 使いまくってる話

NetOpsCoding Advent Calendar 2015 18日目のエントリーです. ハードウェア製品を使ったネットワーキングの話.

さまざまなコンポーネントについて YANG Model の標準化が進み,NETCONF 使える気運が高まっています.さらにRESTCONF も待っていて,ハードウェアで高速にルーティングしつつ運用を自動化できそうな予感がしています.

私は,条件がそろえばNETCONF を使います.XML といえど エラーや出力が構造化されていて きちんと取れるのはうれしい.

一方で 日常のネットワーク運用はCLI に頼っていて,判断をはさみつつ手続き (= コマンド) を変えます.このような用途にはNETCONF / RESTCONF はたぶん向きません.大がかりすぎです.

今回は,自動化できないときでも便利な xlogin を紹介します.

( ようやく,これ自動化の話じゃないわ と気づきました… 気にせず行きます すみません )

xlogin ?

rancid に同梱されている clogin, jlogin, flogin, ... のことをまとめて xlogin と呼んでいます.これらはrancid のためのサポートツールで,各種デバイスにログインするためのexpect スクリプトセットです.

ssh / telnet を薄くwrap するだけの単純なexpect ですが,めちゃ便利なので, いまはネットワークデバイスにログインするとき ssh / telnet を呼ぶことはほとんどありません.たぶんrancid 本体より便利です.

つかいかた

ログインする例 (IOS 系)

xlogin はデバイス種別ごとに実行ファイルが分かれており,IOS 系にはclogin を使います.

clogin <デバイス名>

ログインする例 (IOS 系)

パスワードを自動入力しつつ telnet でログインしています.よく見れば,ログインと同時にenable になっていることがわかると思います.

上はQuagga の例ですが,IOS でも同じ動きです.

複数デバイスにコマンド実行する例 (Junos)

複数のJunos に対し,show version を実行します.Junos にはjlogin を使います.

jlogin -c "show version" <デバイス名1> <デバイス名2>

複数デバイスにコマンド実行する例 (Junos)

2台のJunos に順にssh ログインし,show version を実行し,ログアウトします.結果は標準出力されるので,grep に渡したり,ファイルにリダイレクトしたり,| vi - して開く → 編集 → 保存したりできます.

よく見ると ssh のまえにtelnet を試しているのがわかると思いますが,xlogin はデフォルトでは「telnet / ssh 両方を試してログインできるほうを使う」という動きをします.

初めてssh するデバイスについては,自動でhostkey のfingerprint を~/.ssh/known_hosts に保存します.fingerprint が違っていることを検出した場合はログインしません.

複数コマンドを実行する例 (IOS-XR)

IOS-XR にログインし,interface description を変更します.

! ./cmd
show interfaces description

configure
interface Gi0/0/0/0
description interface1
commit
exit
exit

show interfaces description
exit

上のような一連のコマンド群を実行する必要がありますが,ファイルに書いておいてxlogin に渡すだけ.

clogin -x <コマンドファイル> <デバイス名>

複数コマンドを実行する例 (IOS-XR)

単純に逐次実行するだけなので,失敗時にrollback しない点に注意してください

  • バイス固有のコメント行をいれておくと便利
  • 引数をふやせば,複数デバイスに対して実行できる
  • jlogin -c "show version; show chassis hardware" router1 のようにコマンドラインからも渡せる
  • vty にコピー&ペーストしたときのバッファ溢れが気になる時など,vi cmd; clogin -x cmd <デバイス名> すれば安心.プロンプトが返ってきたら1行実行,また1行実行…という動きをする

以上,xlogin の使用例をならべてみました.

clogin, jlogin, ... を使い分けるのは 少々めんどうですが,便利そうじゃないですか?

対応デバイス

xlogin は対応デバイスが多くてスゴい.めんどくさいプロンプト処理をやってくれてます.

xlogin Vendor
alogin Alteon WebOS switch login
anlogin Arbor Networks login
avologin Avocent login
blogin Bay Networks(Nortel) login
clogin Cisco login
complogin Compass login
dllogin D-Link login
elogin ADC EZT3 login
flogin foundry login
fnlogin fortinet login
hlogin hp login
htlogin Hitachi router login
jlogin juniper login
mrvlogin MRV optical switch login
mtlogin MikroTik router login
nlogin netscreen login
nslogin Netscaler login
panlogin Palo Alto Networks login (based on alogin)
rivlogin Riverstone (and Enterasys SSR) login
rscmd Riverstone Networks Automated login
tlogin Netopis login
wlogin Cisco Wireless Lan Controller login
xilogin Xirrus login

残念なのは,github などにレポジトリがなくてソーシャルコーディングできないこと. ここにないデバイスをサポートしても,本家にマージしてもらうのがつらい.

認証情報

認証情報の持たせかたについても触れておきましょう.

認証情報は,あらかじめ~/.cloginrc という設定ファイルに書いておかないといけません.

  • ログイン方法 (ssh or telnet)
  • 接続先ポート
  • ユーザー名
  • ログインパスワード / enable シークレット
  • ...

などを書いておきます.ログインする際 「xlogin に渡したデバイス を上から順に探し,最初にマッチした設定を使う」という動きをします. サーチドメインをつけた名前や,A レコードを引いたあとのIP アドレスではないことに注意です.デバイス名にはワイルドカードが使えます.

# ~/.cloginrc

add method    router4  {telnet:2605}
add password  router4  {zebra} {zebra}

# default

add user      *        {koji}
add password  *        {login_passwd} {enable_secret}

~/.cloginrc が他者に読まれそうなパーミッションであれば,xlogin 実行時に警告します.

設定方法の詳細はrancid web サイト にあります.プロンプトのカスタマイズや enable するかどうかなど,けっこう細かく設定できます.

その他のヒント

インストール

  • deb パッケージがあるディストリビューションが多い.Debian, Ubuntu などはapt-get 一発
  • rpm パッケージは (たぶん)ないので,自分でつくらないといけない
  • OSX であれば brew にある
  • ping 127.0.0.1 の応答がないと./configure がコケる.OSXfirewall など注意
  • ソースパッケージからビルドしてもよい.なぜか./configure でxlogin が生成される.「./bin/*login だけ$PATH のどこかにコピー」とかよくやる

~/.cloginrc をやめる

ファイルに認証情報を書くのは セキュリティ上いまいちなので,私はvault に保管しています. 完全ではありませんが,他者からアクセスする機会を大幅に減らせますし,認証情報へのアクセスログが取れます.ずいぶんマシ.

やりかたは別のエントリー を参照してください.

まとめ

xlogin の使い方とヒントについて説明しました.

ログインするとき ssh / telnet がわりに使うのはもちろんですが,自動でなにかするコードを書くときにも重宝しています. そのコードには「なにかする」ことだけを記述し,認証とコマンド実行をxlogin に切り出せば 少ない手数で見通しよくなりますし,認証レイヤーを共通化できます.

NETCONF を使いたくても,サポートしていないデバイスがあったりで xlogin + CLI パーサー を選ぶ局面はまだまだありそうです.残念ですけどね.

条件を満たすとチャット通知するリマインダーをつくる

NetOpsCoding Advent Calendar 2015 3日目のエントリーです.

最近はネットワーク運用をラクにすることに興味があります.

たとえば RESTCONF のようなI-D が標準され,進化すれば ネットワークへの自動デプロイが実現できると思いますが,それはネットワーク運用の一部でしかありません.

スコープをネットワーク運用だとすれば

  1. オーダーをもらう
  2. ネットワークをつくる
  3. 監視する
  4. トラブル対応する
  5. ネットワークを拡張する
  6. 廃止する

このようなワークフロー全体をラクに回したい.デプロイツールは1つのコンポーネントなのですが,コンポーネントの進化に比べて ワークフローが進化していない(っぽい) ことに少し懸念を持っています.

雑に言えば「このコンポーネントと このコンポーネントを組み合わせれば,ほら,こんなに便利」という知見が足りないと感じています.

前置きが長くなりました.今回はそういう流れの話にしたいと思い, いろんなコンポーネントを組み合わせて リマインダーをつくってみようと思います.

やりたいこと

ネットワークを運用していると「次のタイミングでやりましょう」ということが結構あります.多くの場合は やりたいことを覚えておいて,なにかの際に,もしくは ときどき「あれ,やれるかな」というふうに判断しています.

ただ,残念ながら「あれ」が増えれば覚えきれないし 小さいことは忘れてしまいます.「タイミングがきたら絶対にここを見るから,ここに書いておこう」という場所は限られていますし,TODO リストの2ページ目以降は目に触れることすらありません.

次のステップとしては,多数の「〜のときに〜したい」を覚える代わり,「〜になったら,〜になりそうなら 勝手に通知がくる」が望ましいと思っています.

〜になったら / 〜になりそうなら のパターン

通知が欲しいタイミングと,情報ソースについてはいくつかのパターンが考えられます.

  • 定期ポーリングで条件判定すれば十分

    • ネットワークの状態から判定
    • コンフィグから判定
    • ログから判定
    • 手順書から判定
  • リアルタイムイベントをつかまえて条件判定

    • ログから判定
    • メールなどの通知時に判定

これから,いくつかの実装例を紹介します.

ポーリング: ネットワークの状態から判定

特定ポートからリンクがすべて抜けたら,ハードウェアを交換したいというケースがありますね.

というフローを考えます.

準備

Slack の場合,単純な通知であればSlackbot がおすすめです.

Slackbot

ほかにもIncoming Webhook やReal Time Messaging API がありますが,Slackbot のみ 同居しているHubot にコマンドを送れます.ここでは紹介しませんが「Slack に通知があったらHubot が勝手に手順書をつくってくれる」など 次のワークフローを回すことができるよう,Hubot を起こせるほうをおすすめします.*1

次にSlack 通知用のシェルスクリプトを準備します.トークンが含まれているのでchmod 700 しておきましょう.

# notify.sh

#!/bin/sh

notify() {
  curl -sfd "$1" 'https://codeance.slack.com/services/hooks/slackbot?token=ccvIktQ90VrBP03gzsA6WsJK&channel=%23test' > /dev/null
}

最後に条件判定スクリプト.ネットワークデバイスの状態を取ってくる部分は NETCONF でもNetOpsCoding Advent Calendar 2日目の例 でもなんでも構いません.今回はrancid に含まれるjlogin を使いました.

# poll.sh

#!/bin/sh

. ./notify.sh

poll() {
  jlogin -c 'show interfaces ge-0/0/* descriptions' 192.168.0.76 | egrep \^ge-0/0 | grep -v down > /dev/null
}

poll || notify 'PIC 0/0 交換のチャンスです'

実行例

ge-0/0/* がすべてdown であれば通知されます.

Slack

このスクリプトを,cron.daily のようにrun-parts 経由でcrond から実行すればOK.

リアルタイムイベント: syslog から判定

特定のBGP ピアでは,落ちたときに急ぎで手動オペレーションしないといけない場合があります.

  • ルーティングを確認したいが,ホントに落とすと怒られる → 自然に落ちたときに確認したい
  • max-prefix limit で落ちたら手動でclear しないといけない

残念ながら,いまのところsyslog 以上に有力なプッシュ通知はないと思いますので

  • syslog で特定のイベントを受信したら,HipChat に通知

というフローを考えます.zabbix やsensu のような監視システムを使っている場合は イベントハンドラーを書くことで簡単に実装できますが,syslog-ng などでも難しくありません.大人の事情で監視機能が外部組織にあっても大丈夫.

準備

HipChat であれば カスタムIntegration を使います.Slack とは違って,どのAPI でもHubot にコマンドを送ることができます.

HipChat Integration

次にHipChat 通知用のシェルスクリプトを準備します.トークンが含まれているのでchmod 700 しておきましょう.

# /home/codeout/hipchat/notify.sh

#!/bin/sh

notify() {
  curl -fd "{\"color\":\"green\",\"message\":\"$1\",\"notify\":false,\"message_format\":\"text\"}" -H 'Content-Type: application/json' 'https://codeance.hipchat.com/v2/room/1301314/notification?auth_token=0uOU8ATolyOMpBGx9P04bjLytOcGnZVm8FugRUEA'
}

最後に条件判定スクリプト.syslog-ng の外部プログラムとして動作させます.

# /home/codeout/hipchat/watch.sh

#!/bin/sh

. /home/codeout/hipchat/notify.sh

filter() {
  echo "$1" | egrep -v 'peer 192.168.0.73.*changed state from Established' > /dev/null
}

while read l; do
  filter "$l" || notify 'ピア(192.168.0.73) 落ちた.経路確認だ'
done

条件判定が成功して通知 / 条件判定が失敗 のどちらのケースでも0 を返せるよう,egrep something && notify よりegrep -v something || notify にします.

泥臭いパターンマッチングをしなくていいよう,構造化されたデータでもらいたいのはNetOpsCoding Advent Calendar 1日目のエントリー のとおりですね.

最後にsyslog-ng の設定例.

# /etc/syslog-ng/conf.d/watch.conf

source s_router {
  udp();
};

destination watch {
  program("/home/koji/hipchat/watch.sh");
};

log {
  source(s_router);
  destination(watch);
};

実行例

BGP ピアが落ちると,HipChat に通知が飛びます.

HipChat

ポーリング: コンフィグから判定

たとえば,トランジットやピア一覧に変化があった場合にリマインドして欲しい場合があります.「連絡先一覧 アップデートした?」のように管理情報の更新を促してもらいたい.

「コンフィグはGitLab で管理されていて,適宜更新されている」という仮定のもと

  • 定期的にgit fetch
  • トランジットやピア一覧に変化があったらSlack に通知

というフローを考えます.

準備

GitLab 上のレポジトリを,仮にssh://git@192.168.99.101:10022/codeout/netops.git とします.

まず 手元にgit clone

$ git clone ssh://git@192.168.99.101:10022/codeout/netops.git

さらに 空パスフレーズssh key を作り,deploy key として登録しておいてください.

詳しい説明は省略しますが,実装例を diff.rb に置きました. ここではJuniper を仮定していて,description "T: Transit A" のようなタグ(T:) を探す動作になっています.また,display-set 形式のほうがdiff に適しているため,junoser を使って変換しています.

内部にssh private key やSlack トークンを書いているので,chmod 700 しておきましょう.

実行例

トランジットやピア一覧に変化があった場合のみ,diff とともにSlack に通知が飛びます.

Slack

リアルタイム: 手順書から判定

最後に「〜になったら」ではなく「〜になりそうなら」の判定を考えてみます.たとえば「デバイスを再起動するときにあれもやりたい」というのはけっこうあるはず.

仮にGitLab で手順書をつくっているとすると Issue やMerge Request などに手順情報が含まれているはずですので,何かしらの方法でこれを見て判定すればよさそうです.

GitLab にはうまいぐあいにWebHook というしくみがあり,データを更新した際 あらかじめ指定したURL にJSON データをPOST させることができます.JSON データのスキーマはGitLab 独自です.

このWebHook を使えば,リアルタイム判定になってしまいますが手順書から「〜になりそうなら」を抽出することができそうです.*2 このWebHook は,GitHub など 流行のサービスにはだいたい備わっていますので,JSON スキーマにさえ注意すれば同じしくみが使えます.

さて,

  • GitLab のIssue / Merge Request のタイトルやコメントに「再起動」「reboot」の文字があったらSlack に通知する

ということをやってみたいと思います.

準備

GitLab 上でWebHook を設定します.そのURL で待ち受けるWebサービスを,これから作ることになります.

GitLab WebHook

単純な実装例を endpoint.rb に置きました.実際はApache やNginx などと連携して動くWeb アプリが好ましいですが,コンセプトの理解には十分でしょう.

これを実行すると ポート8080 でHTTP リクエストを受け付け,GitLab からPOST されたデータを見て 条件に合う場合のみSlack に通知します.

内部にSlack トークンを書いているため,動かす際にはchmod 700 しておきましょう.

実行例

50行程度の短いコードですが,きちんと動きます.

GitLab WebHook

Merge Request データには2つのブランチ名が入っており,ポーリング: コンフィグから判定 のやりかたの応用でMerge Request 中のdiff を見ることもできます.

まとめ

利用可能なコンポーネントをつかって,人間の代わりにコンピューターに覚えといてもらう しくみをいくつか紹介しました.今回の例ではワークフローをラクに回す ところまでいきませんでしたが,人間が苦手な部分 (=記憶) と,単純判断をコンピューターに任せることができるので 少し幸せになれそうです.

さらに言えば,もう次のステップが見えてますね.

「〜のときに〜したい」の後半.アクション部分.通知の代わりに単純作業をやらせましょう.もしくは 作業ための下準備をやらせましょう.

自動でやっていいこと,結構ありそうじゃないですか?

*1:rate limit にだけ注意.現在 1 メッセージ/s

*2:もちろんポーリング動作にもできますが,実装が複雑になる

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

Hubot にlongest match 機能をつける

ネットワーク運用をしていると,ホストアドレスから経路をルックアップしたいことが結構ある.

たとえばDDoS を受けたとき.いろいろな対処が考えられるが,網内のルーターでパケットフィルターする際に「src address → src prefix の変換をしてからガバッと止めたい」とか.

そんな面倒くさいことはBot にやってもらいたい.

実装のアイデア

ルーターにログインして変換してもいいし,ルーティングテーブルを別に持って検索してもいい.

ルーターにログインしてshow route するのはIO バウンドな処理なのでnode との相性はよさそうだが,「src address が100コ」みたいな場合では遅いと思われる.よくあるルーターだと「まとめて引数を渡して,まとめて結果を受け取る」みたいなことができないため「1コ引数を渡して,1コ結果を受け取る」を100回やらないといけない.

一方,ルーティングテーブルを別に持つのってどう実装しよう?とか自前でpatricia tree つくんの?という面倒くささがある.

MRT TABLE DUMP + patricia tree でやってみる

実は,後者の面倒くさいところを解決してくれるbgpdump2 というツールがある.「node バインディング書けばいいのでは」と思ってやってみたら,結構うまく動くっぽかった.(まだ最小限の実装しかできてないです)

github.com

これを使うとlongest match の結果を返してくれる.

var BGPDump = require('node-bgpdump2');
var bgpdump = new BGPDump('path_to_rib.bz2');
console.log(bgpdump.lookup('8.8.8.8'));

// =>
// { prefix: '8.8.8.0/24',
//   nexthop: '202.249.2.169',
//   origin_as: 15169,
//   as_path: '2497 15169' }

Hubot から使うのもカンタンで,

npm install node-bgpdump2 --save
npm install https://github.com/codeout/node-mrt.git --save

たとえばshow route モドキを返すやつ.

# scripts/route.coffee

BGPDump = require('node-bgpdump2')
MRT = require('node-mrt')
mrt = new MRT('wide')

module.exports = (robot) ->
  robot.respond /route (.*)/, (msg) ->
    mrt.get (text)->
      msg.send text
    , (path)->
      bgpdump = new BGPDump(path)
      msg.send JSON.stringify(bgpdump.lookup(msg.match[1]))

ちなみに node-mrt はarchive.routeviews.org からMRT アーカイブをダウンロードしてキャッシュしてくれるライブラリです.

もうちょい複雑なことを: DDoS 対策手順を作ってもらう

一例だけれど,たとえばDDoS を検知後に

  1. src address → src prefix に変換
  2. src prefix ベースで帯域制限する手順をつくり,GitHub Issue としてオープン

というところまでHubot にお願いしたい.
(今回は例としてsrc prefix だけの条件)

hubot-github-templated-issues というの流用すれば結構ラクにできて,8.8.8.8, 8.8.4.4, 64.6.64.6, 64.6.65.6 からのDDoS を検知したときに

hubot anti ddos packet_filter/ratelimit_2g DDoS対策
src: [
8.8.8.8,
8.8.8.8,
64.6.64.6,
64.6.65.6
]

とHubot にお願いすると,

手順書をIssue 化してくれる.
(↑ ではMRT アーカイブをダウンロードしているが,キャッシュがあればそれをつかう)

https://github.com/codeout/sandbox/issues/108

この手順書はテンプレート にパラメーターを埋めて作ったもので,hubot-github-templated-issues がよしなにやってくれます.

今回つかったスクリプト: scripts/anti_ddos.coffee

まとめ

node-bgpdump2 を使うと,Hubot にlongest match 機能をつけられてけっこう便利だと思う.

bgpdump2 自体,少しコンセプト実装っぽい側面があって完全にライブラリ化されていないため,できたら作者の方に今後のロードマップについて聞きたいところです.

iOS9 のMail.app で使えるcipher,使えないcipher

iOS9 に上げたiphone でimaps できなくなったため,Client Hello が送るcipher suite を調べた.

iOS8.4.1 iOS9.0
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ×
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ×
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ×
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ×
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 ×
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 ×
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA ×
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA ×
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA ×
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 ×
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 ×
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA ×
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ×
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA ×
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ×
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ×
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_RSA_WITH_AES_256_GCM_SHA384 ×
TLS_RSA_WITH_AES_128_GCM_SHA256 ×
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
TLS_ECDHE_RSA_WITH_RC4_128_SHA
TLS_ECDH_ECDSA_WITH_RC4_128_SHA ×
TLS_ECDH_RSA_WITH_RC4_128_SHA ×
TLS_RSA_WITH_RC4_128_SHA
TLS_RSA_WITH_RC4_128_MD5

ちなみにOSX Yosemite (10.10.5) はiOS8.4.1 と同じcipher を送る.

openssl ciphers -tls1 HIGH と比べる

iOS8 & iOS9 & OSX Yosemite で使えるものは以下のcipher (openssl 形式) しかない.

  • ECDHE-ECDSA-AES256-SHA (0xC00A)
  • ECDHE-ECDSA-AES128-SHA (0xC009)
  • ECDHE-ECDSA-DES-CBC3-SHA (0xC008)
  • ECDHE-RSA-AES256-SHA (0xC014)
  • ECDHE-RSA-AES128-SHA (0xC013)
  • ECDHE-RSA-DES-CBC3-SHA (0xC012)
  • DHE-RSA-AES256-SHA (0x0039)
  • DHE-RSA-AES128-SHA (0x0033)
  • EDH-RSA-DES-CBC3-SHA (0x0016)
  • AES256-SHA (0x0035)
  • AES128-SHA (0x002F)
  • DES-CBC3-SHA (0x000A)

https のほうはATS 関連でTLS v1.2 が使えるのに,imaps では使えないのがつらい.

JUNOS でCI する話

ネットワーク運用がどんどん便利になって「手軽にメトリック監視できる」「ネットワークリソース管理がほら簡単」みたいな話はけっこう聞く.ところが,本丸であるネットワーク本体への変更については「慎重に人手で」という運用をしているところが多いと思う.

それが悪いということではなくて,たぶん「頻度低いから手動がコストバランス良い」のような理由があるんだけど,じゃあ「それに合う形でワークフローを良くしたいよね」と思う.

たとえばCI を導入することで,ネットワークデバイスへの変更について議論しやすくなったり,レビューしやすくなるといいなあと.

ネットワークでのCI って難しい

ネットワークデバイスに変更を加える場合,ソフトウェア開発のように「あらかじめふるまいを確認する」のはけっこうなコストがかかる.しかし,変更はコマンドのリストとして表現できて,コマンド文法が既知だから「シンタックスが正しい」ことくらいは事前に確認できるはず.

コマンドが誤っていて手戻りが発生することも日常的にあるので,CI でシンタックスを確認するだけでもだいぶマシになるはず,と思って作ったのがこれ.

github.com

netconf 用xsd からPEG パーサーを自動生成するためのツール群と,生成されたPEG パーサーが含まれていて

  • JUNOS のシンタックスチェック
  • show configuration 形式と| display set 形式の相互変換

ができる.詳しくは JANOG36 でのLT 資料 をご覧ください.

CI やってみると どんな感じか

無料でできる Gitlab + Gitlab-CI を使った例.eBGP neighbor を1つ加え,eBGP 全体を微調整するケースを考える.

サンプルレポジトリー: https://github.com/codeout/junos-ci/tree/gitlab

1. 変更案をGitlab にプッシュする

まず,これから加えたい変更について議論したり レビューしてもらうために,変更したい内容をGitlab に掲載する.

# gitlab プロジェクトをローカルにもってくる
$ git clone ssh://git@192.168.99.100:10022/codeout/junos-ci.git
$ cd junos-ci
...

# いま master ブランチにいる
junos-ci $ git branch
* master

# 変更用ブランチをつくる
junos-ci $ git checkout -b add-neighbor
...

config/junos.conf を修正して変更したい内容を記載する.

junos-ci $ git diff
diff --git a/config/junos.conf b/config/junos.conf
index 1331682..e860cc4 100644
--- a/config/junos.conf
+++ b/config/junos.conf
@@ -14,6 +14,8 @@ protocols {
                 }
                 local-address 198.51.100.1;
             }
+            neighbor 192.0.2.3;
+            invalid statement;  # わざとエラーを混ぜる
         }
     }
 }

ここでテストを回してパスするのを確認 → git commit → Gitlab にプッシュするのが本筋だが,今回はテストが落ちることをみるためにテストせずコミット & プッシュ.

junos-ci $ git commit -am "xxxx 開通準備"
junos-ci $ git push origin add-neighbor

2. Merge Request をつくる

Gitlab プロジェクトページに “Create Merge Request” ボタンが現れる.流れに沿ってMerge Request を作成する. こうすることで,他のメンバーに「こんな変更を加えたいので,議論 / レビューしてほしい」と伝えることができる.

作成が終わると勝手にCI が作動し Gitlab 上に “failed” と表示されるので,すぐシンタックスエラーがあることがわかる.(下図)

また,将来変更を適用した際 コンフリクトが発生しそうな場合も (下の例にはないが) warning が表示される.

さらにリンクをたどれば,どの行がおかしいかわかる.
(この場合 protocols bgp group ebgp-peers invalid statement)

失敗したテストをパスさせるには,適宜修正してコミット & プッシュすればよい.再度CI が作動して “passed” に変化する.

  • Gitlab にプッシュしてから “failed” に気づくと二度手間になるので,Merge Request オーナーがプッシュ前にテストするといい
  • レビューする側はプッシュ前にテストされたかどうかわからないが,Gitlab のCI ステータスが “passed” であれば シンタックスは正しい = セマンティクスだけ議論 / レビューすればいい ことがわかる

3. Gitlab で議論 / レビュー

シンタックスチェックはCI 任せにできるので,変更内容だけを議論 / レビューすればいい.

手動でテストする

サンプルレポジトリーRakefile に,junoser を使ったテストのサンプルがある.

ディレクトリ構造やファイル拡張子がちがうと動かないかもしれないので,適宜修正ください.

junos-ci $ rake
skip ./config/cisco.conf
verifying ./config/junos.conf ... done

議論 / レビュー後のデプロイフロー

ネットワークデバイスのMerge Request にはひとつ問題がある.

  1. Merge Request をマージ
  2. デプロイ
  3. show configuration

を行ったときの 1. と 3. の結果が必ずしも一致しない.セマンティクスは同じでも コマンド構造や順序がちがうかもしれない.今回のようにネットワークデバイスの設定を直接管理している場合,ネットワークデバイスが吐く設定をマスターにしたいということもあって,単に「Merge Request をマージして完了」にできない場合がある.

取りうる戦略がいくつかあって,

  • 変更をmaster ブランチにマージ → 変更後の設定全体をデプロイ
  • 変更をマージしない → 差分だけをデプロイ → トピックブランチ*1 を変更後の設定にrebase

それぞれ少しだけ説明します.

変更をmaster ブランチにマージ → 変更後の設定全体をデプロイ

シンプルなので,こちらがオススメ.

  1. Merge Request をmaster にマージ
  2. マージ結果をload override
  3. show configuration をmaster にcommit

ほぼ コマンド投入漏れが発生しないし,変にコンフリクトしていてもshow | compare などで気づくことができる.1. と3. のセマンティクスの整合は取れていると思われるので,そのままcommit する.

可能な限りこちらに寄せたほうが良いと思う.

変更をマージしない → 差分だけをJuniper にデプロイ → トピックブランチを変更後の設定にrebase

数ステップに分けて投入したい場合など.

junos-ci $ ./bin/patch master add-neighbor
# config/junos.conf -> config/junos.conf

set protocols bgp group ebgp-peers neighbor 192.0.2.3
set protocols bgp group ebgp-peers invalid statement

上のように差分をset コマンドに変換して投入した場合,コンフリクトに気付けない可能性がある.

  1. Merge Request をmaster にマージせず,差分をデプロイ
  2. この時点のshow configuration がトピックブランチと一致するかは保証されない
  3. なので,トピックブランチをrebase して確認する
  4. Merge Request をマージ

rebase 時にコンフリクトした場合,設定順序含めてトピックブランチを整理するのがかなり面倒.ただし大きなMerge Request だと 途中で確認を挟まないといけないため,こちらの戦略を取らざるを得ない.

まとめ

  • junoser を使えばJUNOS でCI できる
  • Gitlab のようなツールを使えば,議論 / レビューが楽になる
  • ネットワークへのデプロイ方法によってマージ方法が変わるが,なるべくマージ → 設定全体をデプロイしたほうがいい

ツールの具体的な設定方法を飛ばして「できた後はどんな感じか?」について書きましたが,ネットワークデバイスの設定をGitlab / Github 管理したらワークフローがどうなるか,CI し始めたらワークフローがどうなるか,イメージしてもらえるとうれしいです.

*1:変更のために作ったブランチ