MarkdownをSlackのBlock Kit UIへ変換する

Github上でMarkdownの文章を管理していると、その内容をSlackへポストしたくなるときがあります。

単純にコピペしてしまうとリンクなどが維持されないため、何らかの方法でMarkdownをSlackのメッセージ形式へ変換する必要があります。

世の中のソリューション

もちろん、この問題は世界共通の悩みなのですでにいろいろなソリューションがあります。

  • Slack で真の Markdown を使う

    今のところ、この実装が一番イケてる気がしますが、Node環境が必要なのでちょっと面倒……

    Block Kit UIになっているのもイケてますね。

  • html-to-mrkdwn

    Githubも実のところ、Slack通知を送る際にこの問題を解決する必要があり、これはその実装のようです。 Markdown -> HTML -> Mrkdwn にしているようです。

    こちらはBlock Kit UIでなく、mrkdwnテキストにゴリゴリ変換しているようです。

わたしのソリューション

Goのソリューションが欲しかったので、作りました。

https://github.com/nabeken/blackfriday-slack-block-kit

GoのMarkdownパーサであるblackfriday v2がAST (Abstruct Syntax Tree)を吐くので、それを元にSlack Block Kit UIのJSONへ変換するライブラリと簡単なツールを書きました。

詳しいことはリポジトリ内のREADMEをどうぞ。

このコードは https://github.com/karriereat/blackfriday-slack をBlock Kitに対応させたものになります。ありがとうございました。

Github Actionsで使用するAWSのクレデンシャルをGithub Appsで安全にキーローテーションする

プライベートのAWSアカウントをGithub Actions上のTerraformで管理するようにしました。 その過程でIAMユーザーのクレデンシャルをGithubのSecretsに保存しつつ、安全にキーローテションする方法をGithub Actionsで実装しました。

Github ActionsでAWSのキーローテーションをする場合はRotate AWS Access Keysが有名です。

READMEでも触れられているように、Github Actionsで使える GITHUB_TOKEN にはSecretsを更新する権限が付与されていません。Personal Access Tokenを発行してセットする方法が推奨されていますが、このトークンはアカウントがアクセス可能なすべてのリポジトリに権限を付与してしまうため安全ではありません。

今回はPersonal Access Tokenの替わりに専用のGithub Appsを作成し、特定のリポジトリだけにインストールすることで 安全にSecretsを更新できるようにしました。

Github Appsを作成する

まずはCreating a GitHub Appを参考に作成します。

  • 与えるパーミッションは “Secrets” - “Read & Write” のみでOK
  • このAppは自分専用なので “Only on this account” でOK

作成できたら、Appの設定画面から秘密鍵を生成し、App IDを控えます。

作成したAppはInstalling GitHub Appsを参考に、このAppで管理したいリポジトリへインストールします。

インストールできたら、リポジトリの “Settings” - “Integration” からインストール済みAppの設定画面へ移動します。この時のURL https://github.com/settings/installations/XXXXXXXXXX の部分が Installation ID になるので、これを控えます。

ここで得た値をリポジトリのSecretsへ設定します。

  • APP_ID: Appの App ID を設定
  • APP_INST_ID: Appの Installation ID を設定
  • APP_GITHUB_PRIV_KEY: Appの秘密鍵を設定
  • AWS_ACCESS_KEY_ID: 対応するAWSの初期クレデンシャル
  • AWS_SECRET_ACCESS_KEY: 同上

Github Actionsを作成する

あとはいつも通り .github/workflows/aws_key_rotate.yml としてActionsをインストールします。

例:

Github Appsの GITHUB_TOKEN を取得する処理は拙作のnabeken/go-github-appsのGithub Actionsで処理しています。

これで、毎週月曜日の日本時間9時に自動的にキーが更新されるようになりました。 動作確認時または即時更新したい場合は actions/aws_key_rotate ブランチにpushしてください。

自前メールホスティングの行く先 (2)

前回の続きです。

まずはOffice 365へ移行できるかを検討

徐に “office 365 imap 移行” と雑に検索。

さすがにちゃんと手順はありますね…顧客を引っぱるには必須ですからね…。 IMAPからの移行というよりかは、IMAPをソースにした定期的な一方向同期のようです。

  • 旧ホストにO365への転送設定を入れる
  • IMAP同期する
  • 最後にMXを向ける (いきなり向けても転送設定はどのみち新しいMXに切り替わるまでの間必要)

で自分以外のメールボックスはさくっと移行できそうです。 問題は…自分のメールボックスですね。30GBある。

とりあえず、雑に何通あるか数えてみました。なんでかというと、O365が提供している同期機能にはメールの数に制限があるためです。

ユーザーのメールボックスから移行できる項目数は最大 500,000 です (メールは新しいものから順に移行されます)。

で、

find Maildir/ -type f | wc -l
1906345

あ…うん…190万通ね…そんな気はしてたよ…。15年分あるからね。SpamAssassinがスパムと判定した数を数えてみよう。

find .Junk -type f | wc -l
195059

1割か…消しても焼け石に水ですね(もちろん移行前に消すけど)。 O365の用意する方法では難しそうなので自分でなんとかする必要がありそう。

たしかimapsyncというOSSがあったはず。 (昔ローカルのdovecotとsyncしてオフラインでメール読む環境作った時に使った)

で、これはOSSというかライセンスがちょっと特殊ですね…

そして、すごく詳しいFAQがあった。さすがに商売としてやっているだけある。

EC2のAmazon Linux向けの手順が一番簡単そう。

当面の方針はEC2のスポットインスタンスを使ってimapsyncを動作させて自前メールサーバからOffice 365(Exchang Online)へ移行かな……

今後

  • MXのTTLを短くしとく (2分くらい?)
  • Azure AD作って無料トライアル中にimapsyncを検証する

約90万通あるINBOXが移行できればたぶん問題ないので本移行の作業に入る。

  • 自前メールシステムからO365へ全転送
  • 自分以外のメールボックスをIMAP同期
  • クライアントをO365へ向ける
  • 自分のメールボックスをimapsyncで移行
  • MXを切り替える
  • 自前メールシステムをバックアップ
  • 自前メールシステムを破棄

お楽しみに!(めんどくさいけど)

自前メールホスティングの行く先 (1)

最近まったく記事を書いていませんが、いろいろ考えてはいます。 年単位で考えているのがプライベートで運用しているメールサービスの今後です。 ブログのリハビリも兼ねて書き出してみました。

現在の構成はさくらのVPS上のMXが1台で、そのなかでPostfix + Dovecot (IMAP) + LDAPでバーチャルドメインを使用し複数ドメインを収容しています。 もともと家族用に提供していましたが、利用者は私を入れて2名に減少しています。 自宅サーバ時代から数えてすでに15年以上提供していますが、当時IMAPを使うには自前でホスティングするしか選択肢がなかったはずです。

WikipediaによるとGmailは2004年ベータ版開始(招待制)、そしてIMAP対応と独自ドメインが使えるサービス(Google Apps; 現在のG Suite)が2007年でした。

1年ほど前まではMXが2台あり、LDAPもレプリケーションしていましたが、 バックアップ側のOSが老朽化してしまったのを機に潔く1台構成にしました。 しかし、現在運用中のプライマリもDebianのWheezyなので来年の5月までとなっており、対応が必要となってきました。

仕事柄、まっさきに考えるのは自前を諦めてどこかのサービスへ移管する案

  • G Suite (30GB 6000円/年/ユーザー)
    • ユーザーによっては30GB付近にいる(というか私)。追加料金をみると20GB追加で350円。
    • 仕事でも使っているので慣れている。
    • プライベートでのアカウントではすてに1TBのドライブ容量を買っているのでちょっと納得がいかない…
  • Office 365 (50GB 6480円/年/ユーザー)
    • G Suiteより月額で40円高いが、メールボックスの容量は足りているので追加の必要はなし。
    • 金額面でもG Suiteより安い。
    • しかし、私はGmailのほうが慣れているので、たぶん全転送する
    • 仕事ではあんまり使えていないでAzureADとかの勉強にいいかも (←前向き)
  • さくらのメールボックス (10GB 1029円/年)
    • 安いが容量が圧倒的に足りないし増やせない…
  • Amazon WorkMail (50GB $60/年/ユーザー)
    • 誰か使っている人いるのだろうか…?ユーザ管理にディレクトリが必要そうなので実際には数人レベルで使うものじゃなさそう。

4つ上げたところで、もしここから選ぶならOffice 365になりそうですね。

このまま自前でいく案

金銭面ではMXが1台なのは困るのでもう1台増やしたい。そうすると、月1000円* 2 で年間24000円、上でいう4ユーザー分に相当しますね。 容量は100GBあるので実際には8ユーザー程度くらい?ユーザー数的には10名越えるくらいなら細々と自前でもいいのかもしれません。

技術的な面では、まずOSに依存してしまうとディストリビューションのライフサイクルに強制的に引っ張られてしまうので PostfixとDovecotがいくら枯れていても作業が発生してしまいます。また、その作業自体もさくらのVPSだと安全にというかゆっくり作業ができません (スナップショットを用意するとか、インスタンスを別に用意して最後にIPアドレスを付け替えるなど)。 プライベートだと片手間に検証しつつ………になるので別インスタンスでこつこつ作業して最後に切り替えたい。 ここはコンテナで解決できます。

もう1つ厄介なのが、複数MX構成時のバーチャルアカウントの同期です。LDAPのレプリケーションはもう御免です。2人しかいないのにオーバーキルです。 今はLDAP上のパスワードを変更する昔書いたRailsアプリを必要に応じて使用。

実のところこの問題が解決できずコンテナ化が進んでいません。要件はパスワードの変更が自分でできて、アカウントの同期が簡単にできることです。

案はいくつかあります。

1つ目はコンテナの内部で動かすのであればシステムアカウント (/etc/passwd) でも問題ないので passwdとshadowを生成するWeb I/Fとそれをバラまく仕組みを自分で用意する。Goで書いてAmazon API Gateway -> Lambda -> S3 SSE-KMSに書いて、コンテナ起動時に取得する、など。 ちょっとダルい。

2つ目はサーバレスSTNS。API Gateway + DynamoDBで構成するSTNSをpam経由で使う。過去に検証したところdovecotで使うとstnsがcgoエラーで死ぬので断念しました。 そのまま使うのは厳しいと判断。ただ、Go + cgoでpamモジュールを書くのはよいと感じました。 もし自分がやるならpamモジュール内部ではローカル上のhttpサーバにunix socket経由で問合せるに留めて、以後バックエンドはユーザースペース側に別途用意する設計にするかな…。 ただ、この場合でもWeb I/Fは必要。

3つ目はAmazon Cognit User PoolsまたはAmazon Cloud DirectoryをバックエンドにしたpamモジュールとそれらをいじくるWeb I/Fを書く。 技術的には面白いし本命感ありますが、ユーザー2名なのにそこまで感がハンパない。

4つ目は……自前LDAPの代わりにAzureADを使う。だったら黙ってOffice 365使うよね…

結局のところ、どの案も手を動かす必要がありますね。

それで

私は自前メールサーバの辞め時を探しているのかもしれません。 今のところ、Office 365が最有力候補ですね。自分でも驚いていますが…。 進捗があったらまた続きを。

俺のaws-sdk-goラッパーライブラリ3兄弟

去る2014年12月にStripeによるstripe/aws-goが公開され、 今年2015年1月にAWSがstripe/aws-goを拾い上げ、awslabs/aws-sdk-goとなり (ちなみにライセンスも元のMITからApacheライセンスになった)、そして その半年後の2015年6月にDeveloper Previewながらもついにaws/aws-sdk-goになりました。

前書きが長くなりましたが、ようやく自分のプロジェクトでもgoamzからaws-sdk-goへの移行を 完了させました。その過程で3つのラッパーライブラリを公開したので紹介します。

aws-go-sqs

https://github.com/nabeken/aws-go-sqs

これは以前書いたものです。stripe/aws-go自体に書いたものを 最新の状態へ追随させました。バッチ処理もちゃんと扱えるので便利だと思います。

aws-go-s3

https://github.com/nabeken/aws-go-s3

新作です。aws-go-sqsと同様にfunction optionパターンを適用し、リクエスト用の構造体を それっぽく操作できます。

http.Request.BodyをそのままS3にアップロードする際、SDKが io.ReadSeeker を 要求しているため、今のところ一時ファイルへ書き出してから改めてS3へアップロードする方法 しかありません(署名とかマルチパートアップロードのため?)。

aws-go-s3の今の実装では素朴に一旦 io.ReadAll したものを一時ファイルへ書き出し、それをSDKへ渡す 方式を採用しています。 今後の改善案としては、書き込みと読み出しを同時にできるようにしつつ、io.Seeker もいい感じに扱う ようにしたいところです。

aws-go-dynamodb

https://github.com/nabeken/aws-go-dynamodb

こちらも新作です。同じくfunction optionパターンでやってみました。

Expression APIを使うとクエリは文字列で表現できるようになったのでAPIクライアント的には楽になりました(意味不明なJSONを組み立てる必要がなくなった)。

DynamoDBで面倒なのはGoの構造体のmarshal, unmarshalです。 goamzではencoding/jsonを流用したmarshaler, unmarshalerがありましたが、 aws-sdk-goではdynamodbattributeパッケージがそれに該当します。

dynamodbattributeパッケージには一つ落とし穴があり、AttributeTypeにSet(StringSet/NumberSet/BinarySet)を使っている場合はこのパッケージでは正しく処理できません。これはオブジェクトがDocument TypeのListとMapで表現されているためです。移行時には注意する必要があります。

aws-go-dynamodbはitem.Unmarshalerインターフェースを用意し、それが実装されていればdynamodbattributeを使わずに このインターフェース経由でunmarshalするようにしています。 実装がなければ dynamodbattribute パッケージを使用します。

aws-sdk-go雑感

  • 1年も掛からずに一気に整備された。AWSの本気を感じた。
  • APIのアップデートがあってもすぐに使用可能になってよい
  • 各サービスのパッケージにはそれぞれAPI部分のインターフェースが自動生成されているのでそれを使うことでテスト時にモックへ差し替えられる
  • このライブラリはプリミティブなものなのでやはりラッパーは自分で書く必要がある