🔑

Copilot SecretsがApp Runnerで使えないのをどうにかする

2021/09/09に公開

こんにちは。エンジニアのMasaruTechです。

前回の記事により、Copilotを利用することでApp Runnerのデプロイを行えるようになりました。
しかし、App RunnerでCopilot Secretsが使えないため秘匿情報をどのように扱うかが問題です。
今回はこの問題をどうにかしてみたいと思います。

App RunnerではCopilot Secretsが使えない

このことはドキュメントにも記載があります。

Request-Driven Web Service はシークレットの利用をサポートしていません。

さすがに秘匿情報をGit管理に含めるわけにはいかないので、何か対処しないとApp Runnerの利用は現実的でありません。
App RunnerにこだわっていないのであればECSを利用するほかのServiceを利用すればよいですが、今回はApp Runnerにこだわってみます。

環境変数を埋め込んでみる

まずはお手軽な環境変数を試してみました。

manifest.ymlのenvironmentsにHOGE: $HOGEのように定義するだけです。
これでデプロイしてみたのですが、残念ながら単なる文字列として展開されてしまいダメでした...

copilot svc deployをすると最終的にはCloudFormationのスタック更新が行われるのですが、そのテンプレートは下記のようになっていました。

         ImageConfiguration:
            Port: !Ref ContainerPort
            RuntimeEnvironmentVariables:
              - Name: COPILOT_APPLICATION_NAME
                Value: !Ref AppName
              - Name: COPILOT_ENVIRONMENT_NAME
                Value: !Ref EnvName
              - Name: COPILOT_SERVICE_NAME
                Value: !Ref WorkloadName
              - Name: HOGE
                Value: "$HOGE"
              - Name: LOG_LEVEL
                Value: "debug"
              - Name: RAILS_ENV
                Value: "production"

ここに環境変数が展開された後の値が入ることを期待していたのですが、Copilotでは単なる文字列としてmanifest.ymlの値をCloudFormationのテンプレートに展開するようです。

CodeBuild内でCloudFormationのテンプレートを直接編集する

環境変数の展開ができないのでこの方法を諦めようかと思いましたが、別途Copilot Pipelineを触っているときにひらめきました。

まず、CopilotでPipelineを構築するとざっくりですが下記のような構成ができあがります。
(copilot pipeline init & updateした状態)

CodePipelineの各ステージでは下記を行います。

  • Source Stage
    • 指定したソース元(ここではGitHub)から入力としてソースコードを取得
  • Build Stage
    • copilot svc/job packageコマンドで各envのsvcやjobのCloudFormationテンプレート(YAML)を出力
    • docker buildを行ってイメージをECRへPush
  • Deploy Stage
    • Build Stageで出力されたCloudFormationテンプレートを使用してStackを更新

注目ポイントはここです

copilot svc/job packageコマンドで各envのsvcやjobのCloudFormationテンプレート(YAML)を出力

このcopilot svc/job packageで出力されるCloudFormationテンプレート(YAML)を利用してデプロイが行われるので、このテンプレートを加工すればよいのではないかという超荒技です。

しかも、CodeBuildではenv.parameter-storeを利用するとSSMの値を環境変数へ展開してくれます。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.parameter-store
これならcopilot secret initで登録したParameter Storeの値をApp Runnerでも無理矢理ですが利用できます。

buildspec.ymlで下記のようにしてcopilot svc packageした直後にsedで対象の場所に挿入します。

buildspec.yml
# Buildspec runs in the build stage of your pipeline.
version: 0.2
+ env:
+  parameter-store:
+    HOGE: /copilot/sampleapp/prod/secrets/HOGE

・・・

  - >
    for env in $envs; do
      for svc in $svcs; do
      ./copilot-linux svc package -n $svc -e $env --output-dir './infrastructure' --tag $tag;
+      if [ "$svc" = "app" ]; then
+        sed -i -e "109i \              - Name: HOGE\n                Value: '"$HOGE"'" ./infrastructure/$svc-$env.stack.yml
+      fi
      done;

109iのように挿入する箇所の行番号を書かないといけないので何か変更あるとすぐズレてしまい貧弱極まりないですが、とりあえず目的は達成できました。(なのでご利用は計画的に)

初めはRubyが使えるのでYAMLの編集をYAML読み込み→対象箇所へ差し込み→YAML書き出しで実現しようとしたのですが、この方法では最終的にCloudFormationのテンプレートとしてvalidなYAMLを書き出せずsedに逃げてしまいました。
AmazonLinux2イメージの古いlibyamlパッケージに苦しめられたりしてそこに行くまでも一苦労だったのですが...

CloudFormationからParameter Storeの値を参照する

さすがに上記の方法だと無理矢理すぎるのでもう少しスマートに解決したいです。

そこでCloudFormationについて調べているとParameter Storeなどから動的に値を参照する記法がありました。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references.html

Parameter StoreのSecure Stringを参照するにはresolve:ssm-secureを利用すればいけます。
これでいける!と思ったのですがドキュメントに下記のような表記が

Secure String のための動的なパラメータパターンをサポートするリソース
ssm-secure の動的な参照パターンをサポートするリソースには、現在以下のものがあります。

以下のものの中にApp Runnerはないです。ドキュメントの不備の可能性もあるので一応指定してみましたが

SSM Secure reference is not supported in: [AWS::AppRunner::Service/Properties/SourceConfiguration/ImageRepository/ImageConfiguration/RuntimeEnvironmentVariables]

やはりダメでしたね 🥺

しかし、もう少しドキュメントを読み進めるとSecrets Managerの値を参照する方もありました。
こちらにはParameter Storeのように制限はないようです。

manifest.ymlにHOGE: '{{resolve:secretsmanager:SampleHoge:SecretString:hoge}}'のように指定するとApp Runner側で見事にSecrets Managerの値が展開されました!

copilot secretはSecrets Managerには対応していないので登録などはコンソールやCLIなどでやる必要がありますが、manifest.ymlの書き方を少し変えるだけで対応できるので方法としてはよさそうです。

※Secrets ManagerはParameter Storeと違って値の保存も課金対象ですので、微々たる額ですがそのあたりも利用の際は気を付けましょう。
(CloudFormationのresolve:secretsmanagerでの参照がAPIコールにあたるのかで参照の金額も変わりそうですが)

番外編

RailsのCredentialsのようなしくみを利用すればよいのでは?

Railsをお使いであればまず初めに思い付くのはこれですね。
RailsのCredentialsはそれまでのゴタゴタや使いにくさなどあり使っている人は少ない印象ですが、Rails 6でさらに拡張されて改善されたようなのでRails 6をお使いであれば候補としてはありだと思います。

今回はこういった機構がない場合も想定してなるべくCopilotで実現してみたかったので、この方法は見送ってます。

まとめ

今回はCloudFormationのresolve:secretsmanagerや、テンプレートを直接編集する荒技などで対応してみました。
App RunnerでもECSと同様にParameter Storeの値を利用できれば最高でしたが、まだ新しいサービスなので足りない部分があるのはしょうがないことです。
今の所は少し工夫して回避するとして、きっとそんなことをしているうちにAWSさんが頑張ってくれるはず |ω・)チラ (1ヵ月ぶり2回目)

採用情報

レンティオでは絶賛、エンジニアを募集しています!
https://www.wantedly.com/companies/rentio

Discussion