目の前に僕らの道がある

勉強会とか、技術的にはまったことのメモ

AWS Signerにおけるコンテナ署名の実装

この記事は3-shake Advent Calendar 2024の22日目の記事です。

AWS Signerを使ったコンテナイメージの署名処理を扱った案件があったのでこちらの紹介となります。ただ、後述するように完成には至ってないです。それでもAWS Signerを使った署名処理と署名検証についての概要をお伝えできるかなと思います。

今回のシステムはAWS ECS で Web サービスを運用しています。GitHub Actions を利用してデプロイを行っています。構成としては至ってベーシックな形になっています。 今回、コンテナイメージのセキュリティ強化のため、ECR に保存されているイメージが改竄されていないことを保証する要件が追加されました。この記事では、AWS Signer を用いたコンテナイメージの署名と検証の実装、そして現状の課題と今後について記述します。

AWS Signerとは

AWS Signer はフルマネージドなコード署名サービスです。従来は Lambda 関数などで利用されていましたが、2023年の6月にECRのイメージ署名にも対応しました。

Notary ProjectのNotation CLIを用いることで、ECRに保存されているコンテナイメージを署名することができ、署名ファイルをコンテナイメージとともにECRに保存できます。これによりコンテナイメージの真正性と完全性を検証することができます。

なお、AWS Signerによるイメージ署名に゙関してはNRI ネットコム様のスライドに詳しく書かれているのでこちらを参照するとより理解が深まります。

デプロイフロー

変更前

デプロイフローとしてはGitHub Actionsでレポジトリ内のソースをdocker buildしたものをECRにpushし、ECS Serviceにデプロイするシンプルなワークフローになります。

変更前

変更後

このワークフローにコンテナイメージ署名の処理を追加します。notationコマンドにSigner Profileのarnを指定して、署名と検証をそれぞれ行う形になります。

今回は、GitHub Actions ワークフローに AWS Signer を使った処理を組み込みます。ECRにpushしたイメージに対して署名を行うように変更しました。署名したあとに署名検証を行うことになります。

後述しますが、これだけだと本来は不完全なものです。

変更後

実装

ここから実装を見て行きます。先述したワークフローに帰るために以下の変更が必要となります。

  • インフラ側
    • AWS Signer Profileの追加
    • デプロイ用IAM RoleにAWS Signer Profileへのアクセス権の追加
  • デプロイ側
    • 署名処理の追加

Terraform

インフラ側の変更を見ていきましょう。追加箇所としてはSigner Profileの追加とGitHub Actions用のIAM Policyへの権限追加となります。変更箇所以外は今回は割愛しています。

platform_idを"Notation-OCI-SHA384-ECDSA"に指定してSigner Profileを作成します。レポジトリ名をProfile名にしており、レポジトリ名が - 区切りで、Profile名が - を使えないという事情で _ への置換処理をしています。

  • Siner Profile
resource "aws_signer_signing_profile" "main" {
  platform_id = "Notation-OCI-SHA384-ECDSA"
  # profile名に-が使えないので置換
  name = replace(var.repository_name, "-", "_")
}

先に作ったSigner Profileへの"signer:GetSigningProfile"と"signer:SignPayload"の許可をデプロイ用のRoleのPolicyに付与します。

data "aws_iam_policy_document" "deploy_policy" {
  #前略
  # イメージ署名
  # Inline policies for Signer - AWS Signer
  # https://docs.aws.amazon.com/ja_jp/signer/latest/developerguide/authen-inlinepolicies.html
  statement {
    sid    = "SignImage"
    effect = "Allow"
    actions = [
      "signer:GetSigningProfile",
      "signer:SignPayload"
    ]
    resources = [
      var.signer_profile_arn
    ]
  }
  # 後略
}

デプロイ

signer policyのファイルをあらかじめ作っておきます。このPolicyを利用して、署名検証を行います。

{
    "version":"1.0",
    "trustPolicies":[
      {
          "name":"aws-signer-tp",
          "registryScopes":[
            "*"
          ],
          "signatureVerification":{
            "level":"strict"
          },
          "trustStores":[
            "signingAuthority:aws-signer-ts"
          ],
          "trustedIdentities":[
            "arn:aws:signer:${region}:${account_id}:/signing-profiles/${profile_name}"
          ]
      }
    ]
}

既存のECSのデプロイワークフローにnotationのインストール、イメージ署名処理、イメージ署名検証の処理を追記します。リリースブランチにpushされたことを契機にデプロイが走る形です。

name: Deploy to ECS

on:
  push:
    branches: ['release']

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: ${レポジトリ名}
  SIGNER_PROFILE_ARN: ${Signer Profile ARN}
  SIGNER_POLICY_JSON: .github/aws/signer_policy.json

jobs:
  deploy:
    name: Deploy to ECR, ECS
    runs-on: ubuntu-latest

    steps:

      ### 前略
      - name: Setup Notation
        run: |
          wget https://d2hvyiie56hcat.cloudfront.net/linux/amd64/installer/deb/latest/aws-signer-notation-cli_amd64.deb
          sudo dpkg -i aws-signer-notation-cli_amd64.deb

      - name: Sign image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          notation sign $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --plugin "com.amazonaws.signer.notation.plugin" --id "$SIGNER_PROFILE_ARN"

      - name: Verify image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          notation policy import $SIGNER_POLICY_JSON
          notation verify $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
      ### 後略

課題

ここまででイメージの署名処理および署名検証の実装はできました。しかしながら、いくつか課題があります。

CIとCDの分離

先の実装を見るとわかるのですが、署名したイメージを即時署名検証していることがわかります。これは同じイメージに対して行われているため、実質的な検証にはなっていません。真の改竄検知のためには、CI/CD パイプラインを分離し、デプロイ時に別途署名検証を行う必要があります。

また、pushしたコンテナイメージの脆弱性チェックもデプロイ前に行うことが望ましいです。

そこで下記のように変更したいところです。 ただ、デプロイのフローが変わってしまうので、調整が必要でまだ手をつけていない状態になります。

理想

正規手順以外でデプロイされたイメージの検証

さらに、正規のデプロイフロー以外で起動されたタスクのイメージ検証も課題です。署名されていないイメージが起動されていても何もチェックができていない状態です。

これに対するアプローチとしては、EventBridgeでタスクが起動したイベントを拾って、イメージの署名をチェックし、検証できなかったものに゙関しては処理を行う(タスクの停止や通知など)という方法があります。これはContainers on AWSで紹介されているので、この方法を実装できたらと考えています。

署名検証のサービス統合

ここまで見ていて気付いたかもしれませんが、ECS Serviceがタスクを起動するときに署名されているかどうかをチェックするようにECSサービスと統合されていれば、独自に署名検証を実装する必要はありません。

このへん、Google CloudのBinary Authorizationはサービスと統合されているので、署名検証を自前で書く必要がないと理解してます。AWSもサービスと統合して楽に使えるようになることを期待してます。

まとめ

現状でできていることは以下のとおりです。

  • ECRへpushしたイメージの署名処理

現状課題となっているものは以下のとおりです。

  • CI/CDの分離
  • 署名されていないコンテナイメージが起動されていないかのチェック

この記事では、AWS Signer を用いたコンテナイメージの署名実装と、残された課題について説明しました。

まだできていないことが多いですが、まずビルドしたイメージに対して署名を行うという第一歩を踏み出しました。ここから署名検証の仕組みを強化し、よりセキュアなコンテナ運用を実現するために、引き続き改善に取り組んでいきたいと思ってます。

参考リンク