h-otterの備忘録

インフラを中心にコンピュータ関連をいいかんじにやっていきます

n0stackの認証

n0stackの認証を実装するときに、今更パスワード認証はな~~という気持ちがありました。 ただ、WebAuthnやOpenID ConnectをgRPCインターフェイスで実現するのはなかなか難しかったためどうするか頭を悩ませていました。 その際、ちょうどVMにログインするための公開鍵をn0stackに保存するように変更したため、それでn0stackにログインできればいいのではと考えました。 gRPCを利用するときは、たいていSSH鍵もそのPCに入っているはずなので鍵の管理も容易です。 本稿はその認証方法の紹介です。

h-otter.hatenablog.jp

認証方法

認証方法は公開鍵認証の金字塔であるSSHの方法を参考にしています。 以下のファイルがPoCです。

github.com

実際にn0stackで認証する場合、シーケンス図で表すと以下のようになります。

f:id:h-otter:20190922202623p:plain
認証のシーケンス図

また、コードとの関係は

  1. ユーザーが自分の公開鍵とユーザーアカウントに紐づける
  2. n0stackに認証開始を通知
  3. ユーザーがチャレンジを受け取る n0stack/jwt_example_test.go at 46bf0ae443115774a59a498ea3abe02fb0adc657 · n0stack/n0stack · GitHub
  4. ユーザーはチャレンジとSSH秘密鍵をもとにChallenge JWTを作成 n0stack/jwt_example_test.go at 46bf0ae443115774a59a498ea3abe02fb0adc657 · n0stack/n0stack · GitHub
  5. n0stackはユーザーアカウントに紐づいているSSH公開鍵でChallenge JWTを検証、正しければ実際に認証に利用するAuthn JWTを発行 n0stack/jwt_example_test.go at 46bf0ae443115774a59a498ea3abe02fb0adc657 · n0stack/n0stack · GitHub
  6. ユーザーはAuthn JWTを使って利用したいサービスにアクセス
  7. サービスはn0stackからAuthn用の公開鍵を取得
  8. Authn JWTを検証、正しければユーザーにサービスを提供する n0stack/jwt_example_test.go at 46bf0ae443115774a59a498ea3abe02fb0adc657 · n0stack/n0stack · GitHub

といった形になります。 ここでAuthn用の秘密鍵と公開鍵はYubikeyを参考に、一つのシークレットから自動生成することで秘密情報の管理を簡単にしています。 万が一シークレットが漏洩した場合でも、シークレットを切り替えた時点でAuthn用の公開鍵が切り替わるためAuthn JWT無効化され、ユーザーが再発行すればサービスを継続できるため被害を最小限に防ぐことができます。 また、gRPCの :authority ヘッダ (httpにおけるHostヘッダ) によってAuthn用の公開鍵と秘密鍵の組を変えることで、フィッシングへの対策としています。

developers.yubico.com

中間者攻撃に対してはChallenge JWTに :authority を含めることで対策しています。 攻撃者Evilが example.comトークンを取得しようと考えた場合、シーケンス図的には以下のようになります。 Challenge JWTの改ざんは不可能であると仮定すれば、中間者がいることを検知できます。

f:id:h-otter:20190922202550p:plain
中間者攻撃のシーケンス図

クライアントライブラリに悪意がある場合はどうしようもありませんが、それはSSHも同様なので気にしないことにしています。

gRPCにおける実装

先ほどの認証方法で記載した通り、Authn JWTを発行するまでは2 ~ 5の手順に対応して以下のようにメッセージを交換する必要があります。

f:id:h-otter:20190923140823p:plain

そこで、 Bidirectional streaming RPC を利用しています。 これは一つのRPCの通信でTCPのように送受信できるgRPCインターフェイスです。 これによりチャレンジなどをデータベースなどに一時的に保存する必要がなくなるといったメリットがあります。 また、protobufのoneofを使い、メッセージの型を変えています。

developers.google.com

メッセージの定義は n0stack/authentication.proto at ced0429b1c0d3a81feca86596590427c07af1708 · n0stack/n0stack · GitHub でされており、具体的なサーバの実装は n0stack/api.go at ced0429b1c0d3a81feca86596590427c07af1708 · n0stack/n0stack · GitHub のようになっています。 エラーハンドリングなどはまだ雑ですが目をつぶっていただきたいです :pray: :pray: :pray:。 Bidirectional streaming RPC と oneof の実装についてのブログが少なくて少し混乱しましたが、とてもシンプルに記述ができます。

受信側は以下のようになり、 stream.Recv() でメッセージを受け取った後にoneofで指定したメッセージにキャストします。

また、送信は以下のように stream.Send() でメッセージを送信できます。 oneofの都合によりstructのネストが少し深くなりますが、そこを除けば最小限の記述量で済みます。

ユーザー側の実装は n0stack/authn_client.go at v1alpha · n0stack/n0stack · GitHub で行っています。 特筆すべき点はないですが、 Bidirectional streaming RPC によってサーバー側と同様に可読性の高いコードになっています。

まとめ

本稿ではSSHに利用している公開鍵でgRPCの認証を行う方法を紹介しました。 WebAuthnなどによって公開鍵認証が再び注目されている気がします。 CLISSH公開鍵は親和性が高いので、これからgRPCの認証方法として増えてほしいですね。 また、gRPCの Bidirectional streaming RPC は非常に便利でした。 grpc-gatewayなどによってhttpに変換できないことやテストが難しいことなどの欠点はありますが、おすすめです

すべてのインターフェイスが依存する認証方法を確立できたので、今後はクラウド基盤の機能を増やしていく予定です。 その際、v0.2ではリソースの作成ができてからレスポンスを返す同期APIでしたが、様々なフィードバックからとりあえず受理してレスポンスを返す非同期APIにしてほしいという要望が多かったため、そのように変更を加えます。 次の記事ではそこらへんか、すでにたまっている知見のどちらかを書くと思います。 今後ともよろしくお願いします。

ADR: http://docs.n0st.ac/en/master/developer/adr/grpc_authentication.html

n0stackの紹介

n0stack とは

n0stackとは自作クラウド基盤です。

github.com

これは「ちゃんと動くプライベートクラウド基盤を作る」ことを目指しています。 ちゃんと動くことの具体例としては、

  • APIのレスポンスに待たされない
    • OpenStackはmemcachedを入れないと使い物にならないほど遅く、入れても遅いなぁと思う程度に遅い
  • 基盤自体の構築が簡単
    • OpenStackはPackStackなどのオーケストレータを使わないと構築が難しい
  • ソースコードが読みやすい
    • OpenStackは多くの機能を持っているため便利な反面、ソースコードが多いためビルドが遅くデバッグが大変
    • Kubernetesは抽象化が強く、ある interface{} を何が実装しているのかわからないことが多い
  • 内部APIを作らない
  • VM管理だけではなくアプリケーション開発のプラットフォームにする
    • OpenStackはVMがいい感じにたつ以上の機能をなかなか実現できていない
    • KubernetesはOperatorなどの登場などのにより様々なミドルウェアを利用することができ、便利

という感じです。 特にこれといった新規性があるわけではないですが、これらを実現することは広い知見と深い技術力が必要だと感じていて、結構楽しいです。

これらの目的を満たすために以下のような要素技術を利用しています。

  • Golang
    • シングルバイナリでデプロイが簡単
    • 型付きでコードジャンプが便利
  • gRPC
    • API定義が明確
  • etcd
    • リレーションがないためAPI間が疎結合になる
    • 強い一貫性
    • MQよりも管理やデプロイが簡単
  • リソース志向 + RPC
    • Kubernetesはリソース志向のCRDだが、ステートフルなものを管理するのは難しいため

また、n0stackは様々な構成で動くようなアプリケーションにするつもりはありません。 というのも、OpenStackやKubernetesは確かに多くの機能が実装されているため、様々な環境で動かすことができます。 しかし、多くの機能を実装しているということは多くのバグを抱えやすいということです。 特にクラウド基盤は結合した時にバグるということが多く、結局安定の構成 + アプライアンスのサポートで何とかするみたいなことが多いです。 ならば、初めから多くの機能を一つのアプリケーションで実装することをあきらめ、クラウド基盤を自分で実装できるように支援するようなものを作りこんだほうがいいのではないかと考え始めました。 言い換えれば、ウェブアプリケーションフレームワークならぬクラウド基盤フレームワークを作るということです。 こうすることで、VM管理基盤を自作していた時代のように開発の難しさに頭を悩ませることなく、OpenStackのように長いコンフィグを読み解く必要もなく、ソースコードリーディングは簡単にできるといったメリットが考えられます。

今後の予定

自分の文章が拙いことが最近の悩みなので、その練習がてらやったことをブログに書いていく予定です。

ついでに開発風景をyoutube配信とかもしてみています。 いろんな方とコミュニケーションをとれると面白そうですし、他人の作業風景を見てみたいといわれることが多いのでちょっと流してみてます。 面倒になったら適当にやめると思います。

www.youtube.com

現在 v1alpha by h-otter · Pull Request #232 · n0stack/n0stack · GitHub でブレーキングチェンジ中なので使ってみることはお勧めしませんが、作業が進んでいくのを見守ったりコメントをいただければ嬉しいです。 これからn0stackをよろしくお願いします。

ICTSC7の感想

¾ - 3/5 に第7回トラブルシューティングコンテスト (ICTSC7) が開催されました。 今回自分は @palloc と問題リーダーとして半年間準備を行いました。 参加者の感想がまだ集まり切っていませんが、自己評価としては参加者にも楽しんでもらえる良い大会を運営できたのではないかと思っています。 この記事では大会を時系列で振り返ってみようと思います。

ふりかえり

合宿 (10/29 - 10/30)

キックオフミーティングの前に合宿があったような気がします。 問題の案などを出してもらったり、この大会がどのようなものであるかを共有することのほかに、今回は思い切って遊びました。 これが功を奏したのか、割と雑談が増えたのではないかと… (気のせい?)

約4ヶ月の準備期間

そこからは直接全員集まることはなく、ほとんどリモートで作業を行うことになりますが非常に大変でした。 やはり学生の本業は学業 であるはず なので、トラコンのタスクはどうしても優先度に低いものになります。 やってほしいこととやらなければいけないことを分けて認識し、やらなければいけないことは必ずやってもらうことが重要であったと思います。

Hotstage Phase 1 (2/18 - 2/24)

六本木のCiscoさんのオフィスをお借りして準備をしていました。 その際、付箋で全体のタスクをジャンルごとに分け、Pending(作業予定) / Woring (作業中) / Done (終了) の3つのゾーンに分けたかんばん方式で管理しました。 全体の視認性が上がり、アナログがやはり最強であると証明されてしまった気がします。 個人的にはDoneの付箋がどんどん増えていく達成感と、前回と比べて順調に進みすぎているため何か大きな見落としがあるのではないかという不信感がずっと戦っていました。

f:id:h-otter:20170307201435j:plainf:id:h-otter:20170308211616j:plain

Hotstage Phase 2 (2/24 - 3/3)

現地であるNTT 東日本さんの部屋をお借りして実際の設営などを行いました。 リハーサルを行い、点数の調整などの微調整を行いました。 問題の構築などは早々に終わり、微調整や不具合の修正などに時間をさけたことは快挙でした。

f:id:h-otter:20170307202627j:plain

本番 (¾ - 3/5)

やはり、新しいルールや取り組みによるトラブルが発生しましたが、リハーサルも行ったおかげで最低限に抑えられたかと思います。 実際に開催してみてわかるルールなどの改善点が多く見つかったので次回の参考にしようと思います。 TAZ問題(掃除のおばちゃんによる電源OFF)は反響が大きく(・∀・)ニヤニヤ という感じです。

打ち上げ

みんなテンションが高く、盛り上がる余裕があることは信じられないことでした。 各人それぞれはやはり思うところがあると思いますが、少なくともとても笑って打ち上げができるような大会にできてよかったと思います。

f:id:h-otter:20170308212727j:plain (↑ うるさいくらいにテンションが高い一例)

まとめ

今大会は問題リーダーとして参加者に楽しんでもらうということを目標としました。 そのため前回大会の反省にも上がった点も踏まえて以下のことに挑戦しています。

  • 各問題のシナリオの調整
  • ルールの工夫 (http://icttoracon.net/archives/3701)
    • (ルールを乞ってしまったせいで迷惑をかけた @kyontan をはじめとしたコンテストサイトの開発のみなさま本当にありがとうございました。)
  • リアルタイム採点
  • トラブルの内容とどこまで解決してほしいかを明確にする
  • 問題を解くにあたって、ほかの要素を問題のトラブルだと勘違いしないような構成にする
    • 特に物理機材

どれも難しいことでしたが、運営メンバーと協力することでなんとか形にすることができました。 問題リーダーが2人いる構成でしたが、密に連携をとることで円滑に運営メンバーに仕事をしてもらうことができたと思います。

当然、今大会が成功したことは大会にかかわっているすべての方のおかげです。 特に学生の不手際でいつも迷惑をおかけしてしまう大人の皆さまには頭が上がりません。 関係者の皆様方本当にありがとうございました。

参加者の皆さんも、もし楽しんでいただけたのであればまた参加してください。

クライアント Hyper-V の Txオフロードについて

クライアントHyper-Vを使っている際に、sshコネクションが急に切れたりなどいろいろ不安定なことが多かったりします。 特にWiFiではそれが顕著であると感じています。

今回はWiresharkでパケットキャプチャを行い、Checksum Incorrectが多発していたことを確認しました。 Hyper-VインターフェイスTCP, UDPのTxオフロードを無効化したところChecksum Incorrectが多発しなくなったのでメモです。 もちろんパフォーマンスは下がると思いますが、TCPコネクションが切れるよりはましということで…

サーバーではこの問題は特に起こっていないように感じるので、よくわかりません。 また、これで改善するかしないのかはまだ検証中なので、わからないです。 もしバグであるなら修正してほしいですが、もう少し検証してから細かいことを記事にまとめるつもりです。

Zabbix3.0 へのアップグレード

Zabbix 3.0 LTS がリリースされたのでアップグレードしました。ところどころ詰まったのでメモ。

環境

$ uname -a
Linux zabbix 3.13.0-58-generic #97-Ubuntu SMP Wed Jul 8 02:56:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"

方法

バックアップ

データベースや設定ファイルをバックアップします。自分はHyper-V上に構築しているためチェックポイントとって終了。

リポジトリの更新

Zabbix 3.0のリポジトリをドキュメント通りに更新します。このとき念のため古いリポジトリは削除します。

3 Installation from packages [Zabbix Documentation 3.0]

$ sudo apt-get remove zabbix-release
$ wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.0-1+trusty_all.deb
$ sudo dpkg -i zabbix-release_3.0-1+trusty_all.deb
$ sudo apt-get update

Upgrade

必要なパッケージを適宜インストールします。アップグレードだけであれば以下のコマンドでいいようです。更新中に設定ファイルは古いものを維持するか新しいもので上書きするか聞かれます。

$ sudo apt-get --only-upgrade install zabbix*

設定

基本的に2.2や2.4を新規インストールするときと同様のものを設定します。

タイムゾーン

/etc/apache2/conf-available/zabbix.conf

php_value date.timezone Asia/Tokyo

データベースの設定

[password]に自分が設定したパスワードを入力してください。

/etc/zabbix/zabbix_server.conf

# DBPassword=

DBPassword=[password]

フロントエンドのデータベースの設定は前のものが引き継がれます。もしアップグレードとMySQLのパスワードの変更を同時にやってしまったかわいそうな人(自分)は初期設定画面も表示されず、エラー画面が表示されるだけなので/usr/share/zabbix/conf/zabbix.conf.phpを自分で編集してください。

再起動

$ sudo service zabbix_server restart
$ sudo service apache2 reload

お疲れ様でした。

Ubuntu11.10 on Hyper-V

Exploit Exercises

これをHyper-Vにインストールしようと思ったらなかなかうまくいかなかったのでメモ。

環境

方法

  • ネットワーク アダプタレガシ ネットワーク アダプター
  • SCSI コントローラーを削除

f:id:h-otter:20160120222838p:plain

WinbindでDNSの動的更新

過去の記事で疑問に思っていたことが解決しました。

h-otter.hatenablog.jp

症状

ドメインに参加したときに以下のようなエラーが出力されていた問題です。

$ sudo net ads join -U Administrator 
Enter Administrator's password:
Using short domain name -- DOMAIN
Joined 'client' to dns domain 'domain.local'
No DNS domain configured for client. Unable to perform DNS Update.
DNS update failed: NT_STATUS_INVALID_PARAMETER

ADのDNSが自動更新できない…みたいなエラーだと思います。

解決策

  • /etc/network/interfacesdns-searchを指定
  • /etc/hostsドメインを明示

この2つを行えばうまくいきます。具体的には以下の通りです。

/etc/network/interfaces

auto eth0
iface eth0 inet static
        address 192.168.1.10
        netmask 255.255.255.0
        network 192.168.1.0
        broadcast 192.168.1.255
        gateway 192.168.1.254
        # dns-* options are implemented by the resolvconf package, if installed
        dns-nameservers 192.168.1.1
        dns-search domain.local  # <- これ

/etc/hosts

127.0.0.1       localhost
192.168.1.10   client.domain.local     client

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

という感じに更新して、ドメインに参加するだけです。

$ sudo net ads join -U Administrator
Enter Administrator's password:
Using short domain name -- DOMAIN
Joined 'client' to dns domain 'domain.local'