注意
kustomize 2.0.0からsecretGeneratorのcommandsはセキュリティ上の理由から廃止されました。そのため以下で紹介している方法は現在は使用できません。
やりたいこと
-
kustomize の
secretGenerator
を使って、環境変数から厳密に Secret を生成したい- スペースや改行も含めてバイトレベルで同一の値を持たせて生成したい
- 環境変数が存在しないときは
kustomize build
をエラーにさせたい
例えば CircleCI では環境変数として秘密情報を持たせることができます。
この秘密情報を露出しないように Secret を生成する方法として、secretGenerator
を使おうというものです。
なお、kustomize はこの記事を書いている時点で最新の 1.0.8 を使用します。
結論
これでうまくできます。
多分。
問題になるケースがあれば教えてください。
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: bash -euc 'echo -n "$JSON"'
検証手順
いくつかの kustomization.yaml
を用意して、それぞれ別々の secretGenerator
を定義します。
これを検証用のスクリプトから実行し、結果を入力の環境変数と比較して値の同一性を比較します。
検証用のコードは GitHub に置いています。
https://github.com/yuya-takeyama/kustomize-secret-generator-env-problem
検証スクリプトの本体はこちら
https://github.com/yuya-takeyama/kustomize-secret-generator-env-problem/blob/master/bin/test
引数として kustomization.yaml
が存在する overlay ディレクトリのパスとして渡して実行することで、検証を行えます。
git clone してそのまま実行することもできますし、Dockerfile も同梱しているので Linux (Alpine Linux) 環境での検証も行えます。
$ docker run -it yuyat/kustomize-secret-generator-env-problem
この記事中では macOS Sierra での実行結果を中心に記載しつつ、必要に応じて Linux での実行結果についても言及します。
1. echo
まずは素朴に echo
でやってみましょう。
実行すると以下が出力されます。
-
kustomization.yaml
の中身 - 入力として渡した環境変数 (Expected)
- 様々な問題をあぶり出すために、文字列中に改行コードを含み、改行と空白でインデントされた JSON を渡しています
- 改行コード等も含めて違いがわかりやすいよう、Ruby で inspect した結果を出力しています
- 生成された Secret から取り出した値 (Actual)
- Secret には Base64 エンコードした値が保存されるので、それを取り出してデコードした値です
- こちらも inspect した結果です
- Expected と Actual を比較した結果 (OK or NG)
$ ./bin/test 01-echo
kustomization.yaml:
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: echo $JSON
Expected:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
Actual:
"{ \"FOO\": \"FOO BAR\nBAZ\" }\n"
NG
初歩的な問題として、$JSON
をダブルクォートで囲んでいないので、半角スペースが一部消えてしまっています。
またよく見ると、JSON 中のエスケープされた改行コードであった \n
が本物の改行コードに変わってしまっていますし、末尾にも改行が追加されてしまっていることがわかります。
2. ダブルクォートを使って echo
変数をきちんとダブルクォートで囲んだ上で echo
してみます。
$ ./bin/test 02-echo-with-quotes
kustomization.yaml:
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: echo "$JSON"
Expected:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
Actual:
"{\n \"FOO\": \"FOO BAR\nBAZ\"\n}\n"
NG
空白が一部消えてしまう問題は解消されました。
しかし改行コード関連の問題は相変わらずです。
3. printenv
echo
ではなく printenv
を使ってみます。
$ ./bin/test 03-printenv
kustomization.yaml:
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: printenv JSON
Expected:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
Actual:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}\n"
NG
かなり正解に近づきましたが、末尾に改行が追加されてしまっています。
4. gprintenv -0
macOS の printenv
は BSD のものですが、GNU Coreutils に含まれる GNU printenv (gprintenv
) も試してみましょう。
こちらには BSD 版にはない -0
というオプションが含まれています。
(Linux では printenv
として呼び出す必要がありますが、まぁそこは alias か何かで解決することにしましょう)
$ ./bin/test 04-gprintenv-0
kustomization.yaml:
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: gprintenv -0 JSON
Expected:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
Actual:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}\x00"
NG
今度は末尾にヌルバイトが含まれてしまいました。
man gprintenv
には以下のように記載されています。
-0, --null
end each output line with NUL, not newline
JSON としては同一ですが、バイトレベルでの同一性を保証したいので、やはりこれも NG です。
これではパスワード等の文字列を渡したい時には問題になるかもしれません。
bash から echo -n を呼ぶ
printenv
はうまく行かなそうなので、echo をもっと工夫して呼んでみることにします。
$ ./bin/test 05-bash-echo
kustomization.yaml:
secretGenerator:
- name: foo-secret
commands:
SECRET_JSON: bash -euc 'echo -n "$JSON"'
Expected:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
Actual:
"{\n \"FOO\": \"FOO BAR\\nBAZ\"\n}"
OK
うまくいきました!
これは Alpine Linux 上で実行してみても同じ結果が得られます。
ここからは各オプションの詳細をみていきます。
echo -n
これは末尾に改行が追加されるのを防ぐためのものです。
man echo
によると以下のようにあります。
-n Do not print the trailing newline character.
注意しないといけないのは、echo
には /bin/echo
の他に、シェルによってはビルトインコマンドもあり、挙動が異なることです。
手元の macOS 上の Bash 3.2.57 の echo
は Bash ビルトインのコマンドですが、sh の echo は /bin/echo
で、こちらには -n
オプションが存在しません。
ちなみにエスケープされた改行コードが改行に変換されてしまうのは sh
のせいで、Bash ビルトインの echo
ではこの問題もありません。
(Alpine Linux ではデフォルトのシェルが ash で、ash の echo も同様に問題ないようです)
bash -u
これは環境変数が存在しない時にエラーとするためのものです。
試しに検証スクリプトを通さずに kustomize build
すると以下のようにエラーになることがわかります。
$ kustomize build 05-bash-echo
Error: NewResMapFromSecretArgs: makeSecret: commands map[SECRET_JSON:bash -euc 'echo -n "$JSON"']: exit status 1
bash -e
これは Bash スクリプト中でコマンドがエラーになったらその時点でエラーとしてスクリプトの実行を中止するためのオプションです。
今回の場合はあってもなくても特に変わりませんが、まぁあった方が厳密な感じはするので...