Security Engineering Casual Talksで喋ってきた

本日Security Engineering Casual Talks #1というイベントがあったんですが、そこでKMSの利用について喋ってきました。

内容的にはenv-injectorの利用につながるアレコレだったりするんで、個人的には目新しい内容というわけではなかったんですが、これで世の中からDBパスワードとか他サービスのアクセストークンとかがそのままgithubリポジトリに保存されるケースが少しでも減ればよいなぁ、と思っています。


なお、会場では全く関係ない某社のサービスのTシャツを着ていたんですが、誰からツッコまれませんでした。カナシミー

(追記)

  • 某所からプレゼンをiframeで貼ればいいのに、、、という電波を受け取ったので対応
  • 某所から「"復号化"ではなく"復号"だろ、ボケ」というツッコミがきちゃいました。そのとおりだと思います。ゴメンナサイ。プレゼン資料を修正するのは面倒なので、こちらでお詫びさせてください。

(追記その2)

  • 復号化警察の方から追加で「SystemManagerじゃなくてSystemsManagerだ」というツッコミも来たので、もろもろまとめて資料の方、修正させていただきました。

......なれない発表してゴメンナサイ。もう許して下さい。id:kakku22 兄さん。(´;ω;`)ブワッ

golangの名前解決について

okzk.hatenablog.com

こちらのgolangのstatic link化に関する2年前の記事なのですが、先月こんなコメントをいただきました。

golangで書いたアプリケーションのstatic link化 - okzkメモ

そのtagを指定するとどう動く、ってのがイマイチわかりにくい。netパッケージだとnetgoまたはnetcgoを指定することで名前解決の方法が変わったりするし。

2018/03/27 18:36
b.hatena.ne.jp

そんなわけでビルド時にtagでnetgo指定したら、何が起こるかというハナシです。

あまり知られてないと思いますが、golangDNSの名前解決の方法が以下の2種類があります。

  • libcの getaddrinfo を使う
  • pure goの実装

前者のlibcの getaddrinfo を使う場合はdynamic linkになってしまいますが、 CGO_ENABLED=0 を指定した場合や、netgoを指定したらpure go版の実装に切り替わります。

詳細はソースを検索してみてください。
https://github.com/golang/go/search?l=Go&q=netgo&type=Code

cgonetgo のビルドタグをみると、どのソースがビルドされたりされなかったりがわかるのでオモシロイと思います。

私が把握しているnetgo指定をした場合の挙動の違いは以上なのですが、他にもあったら教えていただけるとウレシイです。
こちらからは以上です。

env-injectorをSecrets Managerに対応させました。

AWS Secrets Managerリリースされましたね。
そんなわけでenv-injectorをSecrets Managerに対応させました。

ダウンロードはgithubのリリースページからどうぞ。

使い方は、こんなカンジでSecretが登録されている状態で

$ aws secretsmanager get-secret-value --secret-id prd/db --query SecretString --output text
{"DB_USER":"scott","DB_PASSWORD":"tiger"}

ENV_INJECTOR_SECRET_NAME を指定して任意のコマンド実行すれば、環境変数が設定された状態でそのコマンドが実行できます。

$ export ENV_INJECTOR_SECRET_NAME=prd/db
$ env-injector printenv | grep DB_
DB_USER=scott
DB_PASSWORD=tiger

必要なIAMのポリシーはこんな感じです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prd/db-*"
            ]
        }
    ]
}

もちろん今まで通り、ENV_INJECTOR_PATHENV_INJECTOR_PREFIX での指定も引き続き有効です。


まあココまでだとシンプルなんですけど、Secrets ManagerはRDS等対象コンポーネント毎にシークレットを管理するものっぽいので env-injectorの「アプリケーションで使うシークレットを管理する」という方向性とちょっとズレがあります。

というわけで、複数のSecretやパラメータストアと組み合わせるような機能も追加しました。

以下のようにSecretやパラメータストアの設定のyamlをパラメータストアに保存しておいて

$ aws ssm get-parameter --name /meta/prd/wap.yaml --query Parameter.Value --output text
# secretの名前を指定
- secret_name: prd/db1

  # RDSのsecretとかだと同じkeyでクレデンシャルが保存されるので、
  # 複数になったときに区別をつけるためのprefix(オプション)
  env_prefix: db1

  # 環境変数にする際にkeyを大文字化したい場合に指定する(オプション)
  capitalize: true

- secret_name: prd/db2
  env_prefix: db2
  capitalize: true

# その他、パラメータストアからも読み出し可能
# (ENV_INJECTOR_PATHで指定するのと同じ機能)
- parameter_store_path: /prod/wap

あとは ENV_INJECTOR_META_CONFIGyamlのパスを指定して任意のコマンドを実行するだけ。

$ export ENV_INJECTOR_META_CONFIG=/meta/prd/wap.yaml
$ env-injector printenv
DB1_USERNAME=alice
DB1_PASSWORD=hogehoge
DB1_HOST=xxxx.ap-northeast-1.rds.amazonaws.com
DB1_PORT=3306
(中略)
DB2_USERNAME=bob
DB2_PASSWORD=mogemoge
DB2_HOST=yyyy.ap-northeast-1.rds.amazonaws.com
DB2_PORT=3306
(中略)
SOME_OTHER_CONFIG_FROM_PARAMETER_STORE=hogeohge

これで既にパラメータストアで管理していたクレデンシャルのうち、一部をSecrets Managerに移行する、とかもできるようになりました。

そんなこんなで、引き続きご利用いただけると、ウレシイですー


……対応しといてアレなのですが、Secrets Managerってパラメータストアと違ってオカネかかるし、機能的にも大きく変わらない(lambdaでローリング更新するのはパラメータストアでもできる)し、ぶっちゃけパラメータストアでええのんちゃうん?という気がしています。(;・∀・)

はてさて、どーなんすかね?

golang 1.9.4とCGO_CFLAGS_ALLOW環境変数

golangで1.9.4のセキュリティアップデートでてます。

github.com

cve.mitre.org

ところがアップデートすると、cgoのCFLAG等で制限されたフラグを使ってるライブラリで以下のようなメッセージを吐いてビルドがこけるようになるケースがある模様。(´;ω;`)ブワッ

$ go get github.com/crewjam/go-xmlsec
go build github.com/crewjam/go-xmlsec: invalid flag in #cgo CFLAGS: -w

そんなときは、慌てず騒がずトライアンドエラーで許可するフラグの正規表現つくってCGO_CFLAGS_ALLOW環境変数にいれれば、ビルドはできるようになります。

$ export CGO_CFLAGS_ALLOW="\\A(-w|-UXMLSEC_CRYPTO_DYNAMIC_LOADING)\\z"
$ go get github.com/crewjam/go-xmlsec

でもエラーになったからって何も考えずに-fplugin=とか-plugin=とかを許可するとセキュリティアップデートのイミがなくなるので、フラグのイミはちゃんと確認することをオススメします。

取り急ぎ、以上。

MackerelでのECSのタスクのメトリクスの2018年版

id:kakku22 兄やんから「Mackerel Meetupで発表することになったぜ」と連絡をもらったのですが、そのハナシの流れで、 「コンテナのメトリクスを取るイケてるやり方をブログにしてちょ」といわれてしまったので PoCレベルで恐縮ですがエントリにまとめます。

前回までのおさらい

ECSホストにMackerelエージェントいれて、そのホストで動いているメトリクスを収集するというカンジです。

kakakakakku.hatenablog.com

とりあえずはまあ動くとはいえ、なんとも微妙な点としては以下の通り

  • シェル芸が必要
  • ダッシュボード作りこみが必要
  • ECSホストでMackerelのagentインスコする必要がある(Fargateの場合どーすんの)

今回のやり方

  • サイドカーコンテナでメトリクス収集するエージェントを動かしておく
  • プラグインがエージェントにアクセスしてまとめてカスタムメトリクスとして投稿する

図にするとこんなカンジ

f:id:okzk:20180128212812p:plain

エージェントについて

PoCということでドキュメントもクソもない状況ですけど、ソースはこっち。 github.com

んでラップしたイメージも用意しました。

https://hub.docker.com/r/okzk/mackerel-metrics-collect-agent/


memcachedのメトリクス収集を例にすると、まずはこんなカンジのconfをS3に上げておきます。

[plugin.metrics.memcached]
command = "mackerel-plugin-memcached -host memcached"

confはmackerel-agent.conf互換なので、違和感は少ないと思います。

次にagentのコンテナをmemcachedサイドカーコンテナとして、memcachedにリンクさせて 環境変数CONF_S3_URIs3://YOUR-BUCKET/path/to/memcache-metrics.confを指定して動かします。
タスクロールでs3:GetObjectできるように許可しておいてください。

この時ネットワークモードがbridgeだったらホスト側のポートを0にしてダイナミックポートで動くようにしておきます。
(ネットワークモードがawsvpcやhostだったら、そのまま2018を指定しておきます)

f:id:okzk:20180128213422p:plain

イメージには標準プラグインを同梱していますが、さらに追加したい場合はmkrでインストールできるようにしているので MKR_INSTALL_PACKAGE環境変数でインストールしたいプラグインを指定してください(スペース区切りで複数可)。

プラグインについて

ソースはこっち。PoCなんでドキュメントも(ry

github.com

こんなカンジで、クラスタ名/サービス名/agentのコンテナ名/ネットワークモードを指定してプラグインの設定をして動かせば、イイカンジにタスクのメトリクスを収集してきます。

[plugin.metrics.memcached]
command = "mackerel-plugin-ecs-task-metrics -cluster my-cluster -service dev-okzk -containerName mackerel-metrics-collect-agent -networkMode bridge"

プラグインを動かすホストについて

ここまででプラグインを動かすホストのカスタムメトリクスとしてECSタスクのメトリクスが収集されるようになったんですが、 通常のホストでプラグイン設定をすると、そのホストがふっとんだときに微妙なカンジになっちゃいます。

ということで、その「プラグインを動かすホスト」もECSで動かすことができるようイメージを用意しました。

https://hub.docker.com/r/okzk/mackerel-ecs-task-metrics-collector/

このイメージでは/var/lib/mackerel-agent/idファイルをS3に退避しておいて使いまわすようにしているので、仮想的に一つのホストとして メトリクスを継続して収集できます。

使い方はapikeyとかも含めた完全なconfをS3にアップロードしておきます。

apikey = "mackerelのAPIキーだよ"
cloud_platform = "none"
display_name = "ECS Task Metrics"

[filesystems]
ignore = ".*"

[plugin.metrics.memcached]
command = "mackerel-plugin-ecs-task-metrics -cluster my-cluster -service dev-okzk -containerName mackerel-metrics-collect-agent -networkMode bridge"

IAMロールは以下のモノが必要です。

  • conf等のアクセスのためにs3:GetObject, s3:PutObject
  • agentアクセスのためにec2:DescribeInstances, ecs:ListTasks, ecs:DescribeContainerInstances, ecs:DescribeTasks

次にtask definitionの環境変数で以下のように指定。

f:id:okzk:20180128214719p:plain

最後にサービス設定で最大1台をキープするように実行します。

f:id:okzk:20180128214743p:plain

そーすると、ホストメトリクスとしてイーカンジにメトリクス収集できます。

f:id:okzk:20180128215238p:plain

やったね。

まとめ

S3にconfぶっこんどいて、ポチポチタスク設定すれば、比較的カンタンにイーカンジにメトリクス収集できるようになりました。
awsvpcにも対応しているのでFargateも怖くないよ。


追記(2018-02-05)

ホストのカスタムメトリクスではなく、サービスメトリクスにしてしまうのがいい気もしてきた。
……っと思ったけど、グラフ定義とか#ワイルドカードの扱いとかアレコレ考えると、サービスメトリクスだとやりにくいか。。。
やっぱり、ホストのカスタムメトリクスの方が都合がいいかも(ぉ

追記(2018-02-05 20時ごろ)

Mackerelの公式でコンテナサポートするそうです。そんなわけで本エントリの内容はPoCのママ、Deprecatedになりそうっすね。(´;ω;`)ブワッ

env-injectorを階層化パラメータに対応させた

引き続きコレの件。

github.com

最初に

前のエントリの追記で、 ENV_INJECTOR_MODE に対応とか書いてたけど、どうも git push を忘れていた模様。orz…

今回の修正で大体ユースケースカバーできそうなので、もうこのままなかったことにします(ぉ

階層化パラメータ

DescribeParametersでは対象のパラメータをIAMでリソース制限できないのがちょっとアレかなぁと思って 今までのenv-injectorは、inject対象リスト作成のためにあえて事前に空の環境変数を用意するようにしてました。

でもアップデートで対応された階層化されたパラメータに対してGetParametersByPathを使えば必要なパラメータだけに絞ったアクセス許可をIAMで設定できます。

というわけでenv-injectorでも対応してみました。

必要なIAMのポリシーはこんな感じで

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParametersByPath"
            ],
            "Resource": [
                "arn:aws:ssm:ap-northeast-1:123456789012:parameter/prod/wap"
            ]
        }
    ]
}

こんなカンジでパラメータが設定されているときに

$ aws ssm get-parameters-by-path --with-decryption --path /prod/wap
{
    "Parameters": [
        {
            "Type": "String",
            "Name": "/prod/wap/DB_USER",
            "Value": "scott"
        },
        {
            "Type": "SecureString",
            "Name": "/prod/wap/DB_PASSWORD",
            "Value": "tiger"
        }
    ]
}

ENV_INJECTOR_PATH を指定して実行するだけ。

$ ENV_INJECTOR_PATH=/prod/wap env-injector printenv | grep DB_
DB_USER=scott
DB_PASSWORD=tiger

DescribeParametersのような制限の効かないパーミションも不要ですし、AWS的にも階層化されたパラメータ推しっぽいので今後はこっちを使うとイイトオモイマス。

意図しないパラメータがinjectされるかも、、、という懸念は残りますが、、、ま、いっか、ということで。(;・∀・)

そんなわけで今後もenv-injectorをよろしくお願いします。

AWS環境下で動かすDockerのイメージのENTRYPOINTに仕込んでおくと本当に便利なので、是非使ってみてください

env-injector作るときに考えてたこと

medium.com

env-injectorが「問題がある」って言われてもうた。ツライ……

というわけで言い訳エントリです。見苦しいですね。


env-injectorの作成時、空の環境変数を作らずに「DescribeParameterでパラメータ一覧ぶっこ抜いてprefixにマッチするものをinjectする」っていうのも考えたんですけど、以下のような理由からやめました。

1. DescribeParameterはIAMのポリシーで必要となる一部のパラメータだけに制限することができない。

開発環境から本番環境のパラメータ一覧が見えたり、別アプリケーションのパラメータ一覧が見えたからって「だから何?」ってハナシもありますが、 なんとなく嬉しくないかなぁ……と。

2. 意図しない環境変数をinjectしないか?

ssm側のパラメータ一覧を全部読み込むことになるので、ちょっとした確認での実行時に意図しない環境変数まで読み込んでしまうコトを懸念しました。

自分で空の環境変数用意するんなら、必要なものだけinjectするだろうし、意図せずinjectするコトはないだろうと思ってみたり。

3. dockerのentrypoint用を最初から想定

パラメータの一覧の「正」とするモノはアプリケーション側にあった方がいい(パラメータストアを見ないと一覧がワカラナイとかツライ)し、 空の環境変数用意するのは、DockerfileのENVで指定すればそんなに手間じゃない、と考えました。

また環境変数のキーが隠蔽できない件も、DockerfileのENVで記述するような時点で隠蔽するものではないだろうと。


最終的には「id:kakku22兄さんからの宿題で、数ヶ月放置したあげくの回答用のPoCだから何でもいっか」っていう雑な決断ですけどね。。。

ざっくり見たカンジ、スクリプトに組み込むんならexecするenv-injectorより、ssm2envの方が便利だと思います。

そんなこんなでパラメータストア周りのツールでイロイロ選択肢が増えればいいんじゃないかなぁ、と個人的には思います。


(追記: 2017-08-04)

なんか元記事の中の方に気を使わせてしまったみたいでスミマセン。

もともとがやっつけで作ったもなので対応できないユースケースがあるのは当然なので、「イケてない」と思われても仕方ないんですけどね。(;・∀・)

んで、実はこっそりDescribeParametersでパラメータぶっこ抜いて環境変数を設定するモードも用意してみました。

# 以下の値がパラメータストアに保存されてるという前提で
#   - prod.wap.DB_USER=admin
#   - prod.wap.DB_PASSWORD=password
#   - prod.wap.DB_PARAM=param
# こんな環境変数を設定しておく
$ export ENV_INJECTOR_PREFIX=prod.wap
$ export DB_USER=user
$ export DB_PASSWORD=

# mode=empty_only: 空の環境変数(DB_PASSWORD)だけ上書き。デフォルト
$ ENV_INJECTOR_MODE=empty_only env-injector printenv | grep DB_
DB_USER=user
DB_PASSWORD=password

# 当然無指定でも同じ挙動
$ env-injector printenv | grep DB_
DB_USER=user
DB_PASSWORD=password

# mode=aggressive: パラメータストアの変数を全部設定するけど、もともと設定されている環境変数(DB_USER)はそのまま
$ ENV_INJECTOR_MODE=aggressive env-injector printenv | grep DB_
DB_USER=user
DB_PASSWORD=password
DB_PARAM=param

# mode=all: パラメータストアの変数を全部設定する。もともと設定されている環境変数があっても上書き
$ ENV_INJECTOR_MODE=all env-injector printenv | grep DB_
DB_USER=admin
DB_PASSWORD=password
DB_PARAM=param

aggressiveallはGetParametersに加えてDescribeParametersのパーミッションも必要になります。
逆にデフォルトのままだと特に追加のパーミッションもいらない、ということで。

……こ、これで、どや?