生涯未熟

生涯未熟

プログラミングをちょこちょこと。

GoでSSH Managerを作成した際の知見

この記事は Go Advent Calendar 2018 の23日目の記事です。

今回は過去に作成したSSH Managerのお話をさせて頂きます。

github.com

どんなツールなのか?

ざっくり言うとSSH接続情報を管理するツールになります。
今の所パスワード認証のみ対応しておりますが、ゆくゆくは公開鍵認証にも対応していく予定です。

簡単に使い方ですが、まずはSSH接続先設定を追加し、

f:id:syossan:20181222025351p:plain

以下のように設定したSSH設定の名前を指定して、実行することでSSH接続が可能になります。

f:id:syossan:20181222024657p:plain

何故作ったか?

所属している会社で運用しているサーバが、パスワード認証で管理されており、毎回user/passwordを入力するのが面倒だなと思ったのがキッカケでした。
パスワード認証の場合、 .ssh/config での接続管理も出来ないので、だったらサクッとGoでSSH Managerを作ってしまおうと思い立ったのです。

類似のツールがあれば良かったのですが、パスワード認証に対応したツールは見つからず・・・

実装

まずは、実装したいものを大きく3つのステップに分けてみました。

  • 接続名、接続先、ユーザー、パスワードの追加・更新・削除、及びAES暗号化
  • パスワード認証によるSSH接続
  • bash-completionの対応

それではひとつずつ実装を追って行きたいと思います。

接続名、接続先、ユーザー、パスワードの追加・更新・削除、及びAES暗号化

実装のイメージとして、下図のようなフローを考えつつ実装を始めました。

f:id:syossan:20181222115732p:plain

順に流れを追って解説致します。

接続情報管理ファイルの操作

接続情報を保持しておくために、接続情報管理ファイルを置き、そこから接続情報の読み込み等を行うようにしました。
そのため、まずは接続情報管理ファイルの存在を確認し、存在しない場合は生成する処理を実装します。

次に、ファイルの存在を確認できたら読み込み処理に入ります。

読み込み処理は少し複雑で、暗号化された接続情報管理ファイルの復号処理を行っております。
今回はAES暗号を採用しており、ハッシュ化にはSHA-256を用いています。

主に復号は次のステップで行っています。

  1. 暗号・復号に用いる鍵をSHA-256で変換
  2. 暗号化された接続情報管理ファイルの内容をbase64デコードする
  3. 変換された鍵を基にブロック・サイファーでのブロックを生成
  4. CTRモードでの鍵ストリーム生成のために初期化ベクトル、また初期化ベクトルを除いた暗号データを取得
  5. CTRモードで鍵ストリームを生成し、XORすることで平文を取得

あまり暗号には詳しくないのですが、多分この流れでキチンと暗号化されている・・・はずです。(※有識者の方、ご指摘あればお願いします)
この暗号・復号に際して、deeeetさんのこちらの記事がとても参考になりました。この場をお借りして感謝🙇

Go言語と暗号技術(AESからTLS) | SOTA

さて、無事に復号出来たところで、このデータを用いて接続情報の追加・更新・削除を行っていきます。

まずは追加処理から。

こちらも復号と同じプロセスで接続情報を暗号化し、接続情報管理ファイルに書き込みを行っております。
接続情報入力に際してはサクッと実装したかったのでprompterを使っています。便利。

github.com

更新処理はあまり追加処理と変わらないですが、このようになります。

どんどんいきましょう、次は削除処理。

これも指定した接続情報を除外した接続情報配列をファイルに保存し直すという簡単な処理になっています。

この他に、接続情報の一覧表示もあるのですが、説明を省略させていただきます。

パスワード認証によるSSH接続

さて、ここからは保存した接続情報を用いた、パスワード認証によるSSH接続の実装に入ります。
何はともあれ、まずはコードを見ていきましょう。

ガガガっと書かれていますが、こちらも大きく分けて2つの要素で構築されています。

  • SSHセッションの確立と設定
  • SSH擬似端末の設定

SSHセッションの確立と設定

connect 関数でセッションの確立を行っています。
パスワード認証の場合、SSHクライアント設定にてパスワード認証を渡す必要があります。

セッション確立の中でアレ?となるのは HostKeyCallback: ssh.InsecureIgnoreHostKey() のところでしょうかね。
これはSSHセッション確立の際に、finger print verificationをskipしている処理になります。
コメントを読むと It should not be used for production code. と書いてあるんですが、正直使ってるの自分くらいなので怒られたら直します。(あかん

セッションの設定については、入出力の設定とSSH擬似端末の関連付けているところくらいですかね。
あまり特筆すべきことはないですが、入出力設定とかはマストだと思うので書くのを忘れないようにしましょう。

SSH擬似端末の設定

次にSSH擬似端末の設定ですが、ここでいちばん大事なのは擬似端末のモード設定になります。
擬似端末にはRAWモードとCOOKEDモードの2種類があり、何が違うかというと「入力文字データをすぐに送信するか、バッファに溜めてから送信するか」の違いになります。
通常、今回のようなSSHに関するツールを作る際にはRAWモードが使われると思いますが、うっかりCOOKEDモードで動かしてしまうとこんな感じになってしまいます。

f:id:syossan:20181222182630p:plain

気を付けましょう。以下のリンク先を読むとより理解できるかと思います。

d.hatena.ne.jp

また、コード内にコメントとして詳しく書いてありますが、RFC4254を読むことでどのようなパラメータを設定すればよいか?などが分かるので、困った時は読むのをオススメします。

bash-completionの対応

最後にbash-completionの対応に入ります。 bash-completionとはよくあるコマンドの入力補完ですね。今回はサブコマンドや接続名の補完に使っていきたいと思います。
このSSH Managerでは urfave/cli を使っており、それを前提としたお話になりますのであしからず。

github.com

さて、bash-completionの実現には次の2つのステップを踏む必要があります。

  • bash-completion機能の有効化
  • 接続名補完の実装

bash-completion機能の有効化

urfave/cli のREADMEにも丁寧にbash-completion機能についての説明がありますが、やることとしては

になります。

EnableBashCompletionをtrueに設定するのはとても簡単で、以下のように行います。

次に、bash_autocompleteファイルの設置ですが、READMEに詳しい方法が書かれています。

https://github.com/urfave/cli#distribution

要するに、「/etc/bash_completion.d/にbash_autocompleteファイルを置く」という手順になります。
簡単は簡単なのですが、ユーザーにとっては一回限りの行動であったとしてもハードルがあるのは否めません。
そこで、この手順をサブコマンドの一つとして組み込むことにしました。

やっていることは単純で、sudo権限でコマンドを実行してもらい、手順通りに/etc/bash_completion.dディレクトにファイルを設置している形になります。
中でも、sudoのチェックの方法が「uidが0か否か」というのを知らなかったので、実装しながらへぇ〜って唸ってました。

さて、ここからは接続名補完の実装のお話です。
幸いなことに、 urfave/cli ではbash-completion機能を有効化にするだけで、サブコマンド等の補完は実現できます。
ただし、接続名などCLIツールとしての範疇外のところは自分で実装する必要があります。
該当コードとしては以下の部分です。

見て分かるように、やっていることは単純に接続名を出力しているだけですね。
このように補完したい項目をfmt.Printlnで出力してあげるだけで実装できます。よかったですね。

まとめ

このように、様々なハマりどころがあったりしますが、比較的簡単にSSH Managerを作成することが出来ました。
実際に、業務の中で試したりしていますが、今の所なんとか使えるかなーというレベルにはなっています。
ただし、動的な擬似端末サイズの変更などまだまだ実装しきれていないところがありますので、その辺もゆくゆくは実装していきたいと考えています。

以上、長文でしたがお読み頂きありがとうございました。

Goのあるコードを発端とした衝撃の結末

はい、というわけで今回はGoのあるコードから紡がれるストーリーに纏わるお話です。

一体何が起こったのか?

発端はこのツイートでした。

むむー、一体なんじゃらほい?とGoの当該ソースコードを見たら一目で分かりました。

// Bug compatibility with C bcrypt implementations. We only encode 23 of
// the 24 bytes encrypted.
hsh := base64Encode(cipherData[:maxCryptedHashSize])
return hsh, nil

github.com

うおー!たしかに Bug compatibility with C bcrypt implementations. って書いてあるー!
"24バイトから23バイト分を暗号化するよ"ってな感じですね。

ここから旅は始まる

まずはこのコードが生まれたところから遡ってみました。
どうやらGo1以前に実装されたようで、ここらへんで実装された様子が伺えます。

Issue 4964078: code review 4964078: crypto/bcrypt: new package - Code Review

うーーーーーん、どうやら「bcrypt実装を書いてみたぜ!」って感じでガバっとPR投げられてますね・・・
ここのレビュー等で件の24バイトから23バイトへ切り詰めている話は特にされてない雰囲気を感じ取ったので、ソースコードから意図を読み取るのを早々に諦めました。

bcryptについて調べる

次に「bcryptの実装上そうなってるのだろうか?」と思い、bcrypt自体を調べることに。

bcrypt - Wikipedia

安心と信頼のwikipediaですね。
幸いにもアルゴリズムに基づいた実装例があったので読んでみると、

Function bcrypt
   Input:
      cost:     Number (4..31)                      log2(Iterations). e.g. 12 ==> 212 = 4,096 iterations
      salt:     array of Bytes (16 bytes)           random salt
      password: array of Bytes (1..72 bytes)        UTF-8 encoded password
   Output: 
      hash:     array of Bytes (24 bytes)

   //Initialize Blowfish state with expensive key setup algorithm
   state 
  
    
    {\displaystyle \gets }
  
\gets EksBlowfishSetup(cost, salt, password)   

   //Repeatedly encrypt the text "OrpheanBeholderScryDoubt" 64 times
   ctext 
  
    
    {\displaystyle \gets }
  
\gets "OrpheanBeholderScryDoubt"  //24 bytes ==> three 64-bit blocks
   repeat (64)
      ctext 
  
    
    {\displaystyle \gets }
  
\gets EncryptECB(state, ctext) //encrypt using standard Blowfish in ECB mode

   //24-byte ctext is resulting password hash
   return Concatenate(cost, salt, ctext)

うーん、どうやら24bytesを特に切り詰めずにそのまま使ってるな・・・🤔
wikiが段々信用できなくなってきたので、ここは愚直にbcryptの提唱者であるNiels Provos & David Mazieresによる文献を読んでみましょう。

www.usenix.org

1999年の資料らしいのですが、こういうのがネット上に残ってるというのは改めてありがたいことですね。
で、アルゴリズムのところとか読んでみたのですが、ここにも特に言及しているところは無く・・・残念。

「こりゃもうダメかもしれんね」って感じで、半ば諦めつつ広大なネットの海を漂うことにしました。

驚きの記事が

「bcrypt 23bytes 24bytes」とか思考停止状態で検索していると、以下のような記事を見つけました。

hackernoon.com

ここに来るまでにかなりの数の記事を読んでいて、これもきっと書いてないんだろうなと白目になりながら見てたら衝撃の記載が。

Issue 2: Using 23 byte instead of the full 24 byte hash

オッ

As stated before, nearly all bcrypt implementations output a 23 byte long hash. The bcrypt algorithm however generates a 24 byte password hash by encrypting three 8 byte blocks using a password-derived blowfish key. The original reference implementation however choose truncate the hash output, it is rumored the reason is to cap it to a more manageable length of 60 character limit (a strange reason if you ask me). The consensus seems to be that the issue of cutting a hash byte off is not a meaningful degradation in security, so it stays an oddity inherited from the reference implementation.

米語なのでGoogle翻訳様に頼ってみましょう。

前述のように、ほぼすべてのbcrypt実装では、23バイトの長さのハッシュが出力されます。 しかし、bcryptアルゴリズムは、パスワード由来のブローキーを使用して3つの8バイトブロックを暗号化することによって、24バイトのパスワードハッシュを生成します。 しかし、元のリファレンス実装では、ハッシュ出力を切り捨てることを選択します。理由は、それが60文字の制限(あなたが私に尋ねると奇妙な理由)の管理しやすい長さにキャップすることです。 合意は、ハッシュバイトを切り捨てることの問題はセキュリティの意味のある劣化ではないので、参照実装から継承された奇妙なままであると考えられます。

ん????

24バイトのパスワードハッシュを生成します。 しかし、元のリファレンス実装では、ハッシュ出力を切り捨てることを選択します。

え?????

理由は、それが60文字の制限(あなたが私に尋ねると奇妙な理由)の管理しやすい長さにキャップすることです。

マジで??????

ありがたいことにHacker Newsのリンクが貼ってあったので、そちらも一応見てみましょう。

A possible flaw in open-source bcrypt implementations | Hacker News

私はpy-bcryptの作者です。py-bcryptは、それを記述している論文の著者(ProvosとMazieres)のbcryptの元の参照実装の周りの薄いラッパーです。ハッシュの長さの差異は完全に無害です(衝突の可能性は2 ^ -186となります)、リファレンス実装に含まれていました。私はハッシュが少し切り捨てられている理由を正確にはわかりませんが、DavidやNielsは60文字のハッシュが扱いやすい長さだと思っていたと思います。

ほげ〜〜〜〜〜〜〜〜〜〜〜

そして、リファレンス実装を見てみると・・・

cvsweb.openbsd.org

encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext,
    4 * BCRYPT_BLOCKS - 1);

( д) ゚ ゚

なんで・・・なんで-1しとるんや・・・
ということで無事、衝撃の結末に辿り着きました。

まとめ

どうやらGoだけではなく、PythonJavaで実装されているbcryptライブラリも、殆どがこのリファレンス実装に従っているみたいですね。
一体どうしてこうなったのかは完全に謎ですが、なんとかハラオチできる結論に辿り着いたのでヨシとしましょう。

現場からは以上です。

[追記]

初心者Elmerがクソアプリを作った時に気付いたこと

この記事は Elm Advent Calendar 2018 の17日目の記事です。

今回は初心者Elmerが別のアドベントカレンダーでアプリケーションを作ってみた時に気付いたことをつらつらと書いてみます。

syossan.hateblo.jp

github.com

書き始め

以前、Elmに入門してからだいぶ時間が経っていたので、振り返りも兼ねて以下の神資料を読んで記憶を呼び覚ましました。

gitpitch.com

gitpitch.com

こうやって忘れても、ちょっと学ぶだけですぐに使い始められるのがElmの良いところですね。

さて、使い方を思い出したところで、今回の実装で自分にとって課題になりそうなところをリストアップしました。

まだ簡単なTODOリストを作ってみたレベルだったので、この辺りの実装に手こずるかなと。

APIサーバとの疎通

何はともあれコードをば。

大まかな流れとして

  1. Decoderの作成
  2. Decoderを使い、GETリクエストの作成
  3. GETリクエストを送信

ってな感じですね。

実装自体は参考になる記事があったりして、そこまで難しくはなかったのですが、Decoderで少し引っかかるところがありました。
本来的にはDecoderは以下のように作成します。

type alias Job = { name : String, id : Int, completed : Bool }

point : Decoder Job
point =
  map3 Job
    (field "name" string)
    (field "id" int)
    (field "completed" bool)

ただ、この map3 というのがマッピングするRecordのフィールド数に合わせて可変する感じで、例えば5つのフィールドがある場合 map5 としなくてはいけなかったりして、直感的に「なんか微妙だなー」と感じました。
そんな気持ちをTwitterで吐露したところ、Elm教の教祖様から福音がありました。

この json-decode-pipeline ってのを使えば、良い感じにpipelineで表現出来るのか、と試したところ良い感じだったので今回の形になりました。

Elmで分からないところがあれば雑にTwitterに書いとくと誰か答えてくれるかも?

あとは、URLにクエリパラメータを付けなくてはいけなかったのですが、今回は無理くりリクエスト作成時の処理に紛れ込ませました。
時間無かったので結構雑にやったのですが、ここ上手いこと出来る方法を知っている方がいれば情報くださいまし(ぉ

ページャーの実装

次にページャーの実装ですが、 elm-paginate を使いました。
今回では以下のように使用しています。

gist.github.com

大体の処理が PaginatedList を引数にとった Paginate.XXX で完結してますね。簡単。
ただし、ページャーで良くある「○○以降のページは...で表示する」とかは自力で実装する必要があります。難しくないけども。

その他諸々

その他ちっちゃいことなんですが、今回CSSフレームワークのBulmaを使ったんですが、以前TODOアプリを作成した時には elm-bulma を使用してElementを書いてくスタイルがあまり好きではなくて、 bulma.min.css を直接呼ぶ形を採用しました。

あと、やっぱりまだまだElmの日本語記事が少なくて「皆書いてくれ〜〜〜」って思ったりしました。
それでも公式ドキュメントとかちゃんと読めば済む話ではあるんですが、ライブラリとかたまにドキュメントが不親切な場合があるので・・・😇

まとめ

Elmは改めて良い言語だなーと感じましたね。
昔ちらっとReact+Reduxとかでフロント書いたりしましたが、今やれと言われても無理です。
しかし、Elmは学習コストがドチャクソ低いので、少々忘れたとしてもElm Guideをササッとなぞればすぐ書けるのが良き。
そういうところもあって、「普段フロントをあまり書かないサーバサイドの人間」に優しい言語でもあるのかな、と。

あとはコンパイラが優しいですね。「ここって間違えてるんだけど、もしかしてこうじゃない?」って教えてくれるので、「それや〜〜〜〜〜😇」って感じで解決して書けていけるので最高。 バーっと書き出して、実行した時にエラーがドンッと出るのが一番イライラしますからね。精神衛生上良い。

また、ライブラリの充実度がちょっと心配してたんですが、普通に使いたいものは揃ってる感じでした。
ただ、そこまで豊富にあるわけではないので、「このライブラリ使い心地がアレだから別の使お」ってなった時に類似ライブラリがあまり無い感じではありました。

という感じで、初心者が実際にちゃんとしたアプリケーションをElmで作ってみた感想をつらつらと書いてみました。

皆、Elmやろう!!!

ウルヴァリンがハッキングする映画はいいぞ

というわけで、こちらは プログラマーにおすすめしたい映画 Advent Calendar 2018 14日目の記事になります。

君は『ソードフィッシュ』を知っているか?

名前からして何の映画か分からない『ソードフィッシュ

まずはパッケージ画像を見てみましょう。

f:id:syossan:20181212011445j:plain

キャストが濃い!!!

特にジョン・トラボルタなんて濃さの塊ですよね。
パルプ・フィクションでもそうなんですが、トラボルタがいるだけで映画の雰囲気が3割増しで濃くなります。

さて、そんな中一番右にございまするお方は・・・

ウルヴァリンウルヴァリンじゃないか!

はい、というわけでヒュー・ジャックマンが出演しております。
いやー・・・かっこいい・・・雄って感じがして最高ですよね、ね?

ハル・ベリードン・チードルもMarvel映画好きからするとお馴染みの面々ですね。

肝心の映画自体の話に入るのですが、あらすじを簡単に説明すると

『凄腕ハッカーヒュー・ジャックマンジョン・トラボルタハル・ベリーが持ちかけた”ソードフィッシュ計画”なるハッキングでの裏金奪取計画に巻き込まれていく・・・』

という感じです。

大袈裟だけどそそられるプログラミングシーン

あらすじからも分かる通り、ヒュー・ジャックマンが凄腕ハッカーという設定なので何回かハッキングシーンが出てきます。
もう、それが「絶対にそんなん無理やろwww」ってツッコミたくなることうけあいなんですが、どこかそそられてしまう代物。

最初のハッキングは、ジョン・トラボルタがハッキングの腕を試すために並のハッカーが60分かかるセキュリティを60秒で突破しろというもの。
いやいやいやいや、無理無理無理無理!エンジニアの生産性が人によっては100倍差が出るとか言われるけど無理でしょ!

しかも、映画を観たらわかるんですが下半身をゴニョゴニョされながらハッキングさせられるんですよね・・・うらやましい

ただ、そこはウルヴァリン。鍛えた上腕二頭筋でバッチバチにキーボード叩きながらハッキングを成功させてしまいます。すげぇな、おい。

f:id:syossan:20181212013738p:plain
せやな

次のハッキングシーンは、ソードフィッシュ計画に使うワームを作るんですが、そのシーンがまた見物でめちゃくちゃ四苦八苦しながら謎の3DCGと向き合いながらプログラミングしてるんですよね。
荒唐無稽っちゃ荒唐無稽なんですが、ヒュー・ジャックマンが必死こいて頑張ってるのみてると、なんとも言えない「オッ、やっとるな」って気持ちになります。いいですね。


飲酒プログラミングでテンションの上がるヒュー・ジャックマン

正直なところ

ハッキングって要素は添え物で、本番は後半の圧巻な銃撃戦に詰まってます。
後半観てると「一体今までのハッキング云々は何だったんだろう・・・🤔」って感じになることでしょう。
あと無駄にハル・ベリーがエロい

まぁ、そんな感じで普通の映画として観るとまぁまぁな本作ですがエンジニアが観るとハッキングシーンにオッとなるのでオススメです。
Amazon Prime Videoにもあるので、気になった方は観てみましょう!💪

チャレンジングなことをしてみたらエッチなクソアプリが誕生した

どうも、この記事は クソアプリアドベントカレンダー の10日目のようです。

さて、今年ひり出したクソアプリは

エッチなやつです

f:id:syossan:20181210005011p:plain

エッチなリンク

業務で触った技術がなんか活かせないかな〜?と考えていたら、何故かエロクソアプリが誕生していました。不思議ですね。

どんなクソなの?

で、どんなアプリなのかというと「サクッと条件に合うAV女優探してサクッとxvideosかDMMに飛べるやつ」です。便利ですね。

これ以上、特に解説することがなくて困ってしまいましたので、技術的な解説に入りましょう。

技術の話

今回は、フロントをElmで、バックをGo + Lambda + S3 Selectで構築しました。

f:id:syossan:20181210011649p:plain

フロントの話

Elmを採用した理由としては、最近Elmを布教する教祖の人に影響を受けてTODOアプリ以外のもの作ってみてーなーということからやってみました。
実際ガッツリやってみると、コアなところはミニマルで言語仕様をサクッと把握出来るので学習コストがかなり低い言語だなーと改めて感じました。

ただ、パッケージによっては結構扱い方がピーキーなものしかなかったりして、まだそこらへんのエコシステム周りが潤ってない感はありました。
ハマりどころはあるものの良い言語です。皆さんもやりましょう。

あと、CSSフレームワークはBulmaを使いました。名前からして今回のアプリにぴったりですね。

S3のWeb Hostingを使ってるので、デプロイも npm run build して生成されたdist fileを放り込むだけで完了。平易。

さて、今回フロント側ではスケベデータ取得用Lambdaに問い合わせた結果を単純に表示しているだけの簡単な作りになっています。
あまり語ることが無いのでバックエンドの話に移りましょう。

フロントのソースコードはこちら

github.com

 バックエンドの話

バックエンドは手に馴染んだGoを使い、Lambdaに乗せてS3 Selectを用いてCSVデータを取得してくるように作りました。
S3 Selectが前から気になってて、やっとこさまともに使ってみたのですがドキュメントに書いてないハマりどころがあり、めちゃくちゃハマってしまいました・・・

その辺の詳しい話はGoアドベントカレンダーの記事で書こうかなと思うのですが、概要だけ書くと

  • メッセージタイプがRecordsの場合、65000バイトのchunk dataで送信される
  • CSV列の途中に欠損データがある場合、欠損データ行までしか比較演算子の対象とならない

という感じで、特に2つ目は1日溶かすレベルで悩みました・・・

そんなS3 Selectですが、1600件ほどのデータから簡単なクエリで引っ張ってくるだけなら300msくらいで返してくれるので、簡単なデータを扱うならこれでもいいのかなーと思える感じでした。

また、コンソールからSQLも叩けるのでデバッグとかに重宝しました。

S3バケット内のCSVにS3 Selectというタブがあるので

f:id:syossan:20181210015244p:plain

そこからSQL実行できます。

f:id:syossan:20181210015333p:plain

バックエンドのソースコードはこちら

github.com

まとめ

作った期間は2日くらいですが、すごく久々にクソをひり出せて満足感が高かったです。
用意したデータ自体の不備が結構あったので、全部で約1600件のデータしか返せないのが残念ですが、暇を見つけて充足させていきたいと思います。

みんな、エロはいいぞ。

現場からは以上です。