目の前に僕らの道がある

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

Google Cloud で目指す クラウド二刀流エンジニア講座第3回に登壇してきました。

11/18に Google Cloudで目指すクラウド二刀流エンジニア講座第3回 に登壇してきました。

cloudonair.withgoogle.com

第1回でパネルディスカッションに出てきたのに引き続き、今回は Fargate との差分で理解する、Cloud Run のシンプルな魅力 と題して登壇させていただきました。

AWSをもうすでに使ってる方向けにECS Fargateと比較しつつCloud Runのシンプルな魅力を紹介するセッションとなっていました。視聴登録すればこちらから動画が見れるかと思います。資料も後ほどこちらに上がると思います。

内容としては以下のような話をしました。

  • Cloud Runの概要紹介
  • ECS FargateとCloud Runのアプリケーションアーキテクチャ比較
  • ネットワーク機能の紹介
  • セキュリティ機能の紹介
  • 運用監視機能の紹介

今回はCloud Runを使ってない人向けに概要紹介するセッションだったので、深入りできていない部分や説明不足、端折ったところがたくさんあります。これについてはどこかでエントリ書きたいと思ってます。

とにかくCloud Runはいいぞ!ということが伝わっていれば何よりです。Cloud Runの良さを伝えたつもりですが、あくまでなんでも適材適所でFargateが向いているコンテキストに無理やり変える必要はなく、比較検討できる選択肢の一つとしてCloud Runを入れていただけたらと思います。

スライドにもう少し図表を入れた方が伝わったかなとか反省点はありつつも、40分トークは初めてだったのですごくいい経験になりました。今後ももっと大きなところで登壇できるように精進していきます。

クラウド環境におけるシークレットの扱い

この内容は、社内のエンジニア勉強会で話した内容です。

speakerdeck.com

みなさん。プロダクション環境のシークレット情報をどう扱っていますか?

クラウドネイティブなアプリケーション開発において、DBのパスワードや外部APIキーといったシークレットの管理は、セキュリティを確保する上で避けては通れない課題です。

この記事では、アプリケーションとインフラそれぞれの視点から、クラウド環境におけるシークレット管理のアンチパターンとベストプラクティスを探っていきます。

ここで言うシークレットとはDBのパスワードやAPIキーなどの秘匿すべき情報のことを指します。

アプリケーション側の視点

まずは、アプリケーションがどのようにシークレットを扱うべきかを見ていきましょう。

管理のアンチパターン

最初に管理方法のアンチパターンとしては以下のものがあります

  • ソースコードに直接記述
  • 設定ファイルに平文で記述
  • 環境変数に平文で記述(Dockerfileや.envファイルでgit管理するなど)
  • base64エンコードして保存

ソースコードに記述すれば、すべての環境で同じ値しか使えず柔軟性がありません。設定ファイルや環境変数に平文で記述し、それをGitで管理してしまうと、何かのミスでリポジトリが流出した際にシークレットも漏れてしまいます。

また、隠しているつもりでBase64エンコードするのも同様に危険です。Base64は暗号化ではなく、誰でも簡単に元の文字列に戻せるため、平文で保存しているのと大差ありません。

KMSによる暗号化の検討

次に考えられるのが、暗号鍵を使った暗号化です。AWSのKMSやGoogle CloudのCloud KMSといった鍵管理サービスを利用する方法が考えられます。

フローとしては以下のようになりますでしょうか

  • アプリケーション起動時にKMSのから鍵を取得
  • 取得した鍵を利用して暗号化されたシークレットを復号
  • 平文のシークレット情報をアプリで利用する

一見これで良さそうですが、復号処理をアプリケーションの責務にすると、コードが複雑になるだけでなく、KMSの復号権限をアプリケーション自体に付与する必要があり、管理の懸念点が増えてしまいます。

クラウド側のシークレットストアの利用

そこで推奨されるのが、クラウドが提供するシークレット管理の仕組みを利用することです。

  • AWS
    • AWS Secrets Manager
    • AWS Systems Manager Parameter Store(SecureString)
  • Google Cloud
    • Secret Manager

これらのサービスは、ECS FargateやCloud Runなどのコンテナ実行環境と統合されています。コンテナの起動時に、これらのストアに保存されたシークレットを、自動的に環境変数やファイルとしてマウントしてくれるのです。

これによりアプリケーション側では、シークレットがどこで管理されているかを意識することなく、従来通り環境変数やファイルから値を読み込むだけで済むようになり、責務をシンプルに保つことができます。

インフラ側の視点

さて、アプリケーションの課題は解決しました。次に、インフラ側で、そのシークレットストアをどう管理するかという課題に移りましょう。

取れる手段としては主に以下ものが考えられます。

  • 手動でコンソールから設定
  • シークレットの値を平文でIaC管理(tfvarsファイルをgit管理から外す)
  • シークレットの値を暗号化してIaCで管理
  • シークレットストアをIaCで管理、値は手動設定

まず手動で管理ですが、これはこれでありだと思ってます。ただし、扱うシークレットの数が増えてきたときに作業が煩雑であったり、手作業がゆえに起こるリソースタグなどの付け間違いなどのミスが発生しうるので、規模が大きくなると現実的ではありません。

2つ目ですが、シークレットの値だけあつかるtfvarsファイルをgitignoreしてあげることでレポジトリが漏れてもシークレットの値が漏れないことになります。が、うっかりシークレットの値を人為的なミスでコミットしうるので完全に安全とはいいにくいです。

3つ目ですが、これはsops providerを利用するパターンです。これを使うことでKMSキーを利用して暗号/復号がterraformとシームレスに統合できます。一見これで良さそうですが、2点課題があります。

  • KMSリソースを余計に管理なくてはならない
  • Stateには平文で保存される

前者は必要経費としていいとして、後者は課題となります。Terraformにおいてはstateを見る権限がある人にはシークレットも見れてしまうという懸念があります。

シークレットのリソースと値を分離する

この方法の利点は、IaCでシークレット リソースが存在することは管理しつつ、その実際の値はGitの管理下から完全に分離できる点です。初回適用後にコンソールから実際のシークレット値を設定すれば、それ以降 terraform apply を実行しても値がダミー値で上書きされることはありません。

これにより、コードレビューなどで誤ってシークレットが漏洩するリスクを原理的に防ぐことができ、非常にバランスの取れた管理方法と言えます。

以下サンプルコードです。

resource "aws_ssm_parameter" "db_password" {
  type     = "SecureString"
  name     = "/test/db_password"
  value    =  "Dummy"
  lifecycle {
    ignore_changes = [value]
  }
}

まとめ

現時点でのクラウドにおけるシークレット管理のベストプラクティスは、以下のようにまとめることができるでしょう。

  • アプリケーション
    • クラウドのシークレットストア(Secrets Managerなど)と実行環境(ECS, Cloud Runなど)の統合機能を使い、環境変数またはファイルとしてシークレットを読み込む。
  • インフラ(IaC)
    • クラウドのシークレットストアのリソース自体はTerraformで管理する。
    • 実際のシークレットの値は ignore_changes を活用して手動で設定し、Gitの管理から分離する。

もちろん要件によって取りうる手段は変わるとは思います。他になにか良い方法をご存知でしたら教えて下さい。

それでは良いシークレットライフを!

関連ページ

blog.masasuzu.net

Google Cloud で目指す クラウド二刀流エンジニア講座 第1回 でパネルディスカッションに出てきました。

先日、2025年6月4日に開催された 「Google Cloud で目指すクラウド二刀流エンジニア講座」 の第1回にて、「スペシャリストが語る!Google Cloud のメリットを活かすネットワーク、セキュリティのあり方とは?」 と題したパネルディスカッションに登壇いたしました。

イベントから少し時間が経ちましたが、当日の内容を振り返り、話したことや、時間の都合上話しきれなかった点などをまとめていきたいと思います。

cloudonair.withgoogle.com

ページとセッションでの資料は以下のとおりです。

以下パネルディスカッションでお話しした内容と補足を記載します。

続きを読む

Terraform使いがOpenTofuについて入門してみる

この記事はSRETT #11で発表されたものに加筆修正したものです。OpenTofuに関して調べたこととなります。

speakerdeck.com

先日KubeCon + CloudNativeCon North America 2024に行ってきてました。その中で共同開催されていたOpenTofu Dayを見てOpenTofuに関して興味を持ちました。普段はTerraformを利用しており、あまりOpenTofuについては触ってきてないので、この機会に深堀りをしてみたいと思いました。

また、社内活動として技術検証を行っており、私の検証テーマとしてTerraformを中心としたIaC周りの技術調査を行ってるので、ちょうどいい機会だとも思いました。

おことわり

この記事はTerraformを知っている前提で書かれています。そのため細かい説明を省略している箇所があります。

また筆者は普段はTerraformをメインで使用しており、OpenTofuを業務利用はしていません。

OpenTofuとは

2023年8月にTerraformを含めたHashiCorp製品のライセンスの変更を発表したことにより、これを懸念した企業やコミュニティによりOpenTFとしてフォークされます。その後OpenTFの名称はHashiCorp社の商標権への懸念からOpenTofuに改名されます。そのときの議論はissueを見るとたどることができます。

2023年9月にLinux Foundation傘下となります。

Terraformをフォークしたものなので基本的な使い勝手は同じです。コマンド名が terraform から tofu に差し替えられています。

ライセンス問題

前項でさらっとライセンス変更と言いましたが、HashiCorp社は2023年8月に今後のリリースに関してライセンスを変更する旨を発表しました。これはオープンソースライセンスであるMozilla Public License(MPL) v2.0から商用サービスでの利用を制限するBusiness Source License(BUSLあるいはBSL) v1.1に変更するものです。

これに対して、利用企業およびコミュニティが懸念を示し、OpenTofuをフォークしたという流れになります。

HashiCorp社の言い分

従来BSLは本番使用(production use)が制限されます。ただし、ライセンスのParameterとして追加使用許可(Additional Use Grant)をすることによりTerraformと「競合製品」でなければ本番利用の制限はないとしてます。

「競合製品」とは、有料サポート契約を含む第三者に販売される製品で、HashiCorp のライセンス対象製品の有料版の機能と大幅に重複する製品を指します。TerraformでいうところのHCP Terraform(Terraform Cloud)を想定しているのかと思います。

また組織内でTerraformをホストして利用することは「競合製品」とはみなされなません。そのため利用者としては基本的には問題なく利用できるとしてます。

問題となるのはTerraformの機能を有償で提供しているSaaSと読み取れます。

コミュニティの懸念

HashiCorp社が説明したBSLと追加使用許可はあいまいであるとしてます。そのため、自身の行動が許諾範囲内か判断が困難である。「競合製品」の定義やライセンス自体が今後変更されるか不確実であると懸念を示してます。

また、TerraformはOSSの恩恵を受けて成長してきてため、これからもオープンソースソフトウェアであるべきだと信じていると表明しています。

OpenTofuのスポンサー企業としては以下のとおりです。

HarnessはCI/CDまわりのSaaS製品、Gruntworksはterragruntの開発元、Specelift、env0、ScalrはTerraformをホストするSaaSサービスを運営しています。

OpenTofuとTerraformの違い

この項ではそれぞれの違いについて説明していきます。

OpenTofuはTerraform1.6-alphaからフォークされているのでそれまでに実装されていたものは互換があります。

また、Terraform 1.6以降に追加された機能に関しても随時取り込まれています。そのため、1.5までの機能を使っているのであれば素直に移行できるかとは思います。

バージョンごとに移行ガイドがあるので細かくはそれを参照すると良いです。

ただし、別のコードベースで開発がされているので、OpenTofuのみの独自実装もあります。ここではいくつか個人的に気になる違いについてあげていきます。

コマンド

  • 基本的には terraform を tofuに置き換えていただければよいです。サブコマンドは一緒です。
# Terraform
terraform init
terraform plan
terraform apply
terraform destroy

# OpenTofu
tofu init
tofu plan
tofu apply
tofu destroy

ファイル

terraform由来の .tf または .tofu の拡張子のファイルを設定ファイルとして認識します。json形式の .tf.json または .tofu.json の拡張子のファイルも同様です。

同じディレクトリ内に.tf.tofu の両方のファイルがあった場合、.tofu ファイルだけ認識して、.tf ファイルは無視されます。

foo.tf  # <=== このファイルは無視される
foo.tofu

Registry

Terraform同様OpenTofuにもプロバイダーやモジュールのレジストリがあります。

OpenTofu Registryが登場したときに存在したTerraform Providerは反映されています。反映されていないものに関してもissueを立てれば反映されるようです

removedブロック

removedブロックは既存のリソースを削除することなく、stateから削除することができます。

それぞれ下記のように記述できます。下記の例ではAWSインスタンス自体は削除せず、stateから外すことを意図してます。

# Terraform
removed {
  from = aws_instance.example

  lifecycle {
    destroy = false
  }
}

# OpenTofu
removed {
  from = aws_instance.example
}

Terraformではlifecyleブロックでdestroy=falseの記述が必須です。

OpenTofuではremovedブロックを書くだけで stateから削除されます。

removedブロックでやりたいことはstateから削除することなので、単純にリソースを削除したいなら対象resouceブロックを削除すればいいので、Terraformの記述方法のほうがへんな気がします。

State Encryption

Terraformでは平文でStateに保存されてしまうという問題がありましたが、OpenTofuではクライアントサイドで暗号化する機能が追加されてます。クラウドプロバイダーの KMSキーなどを利用してStateを暗号化することができます。

Terraformではたとえsopsプロバイダーで機密情報を暗号化しても、Stateファイルには平文で保存されているので権限があれば機密情報が見えてしまう状態にありました。State自体が暗号化されることにより機密情報をよりセキュアに扱えるようになります。

backendブロックの変数参照

OpenTofuではbackendブロックで変数参照ができます

variable "env" {
  type    = string
}

locals {
  path = "${var.env}/terraform.tfstate"
}

terraform {
  backend "local" {
    path = local.path
  }
}
tofu init -var="env=dev" -reconfigure
tofu plan -var="env=dev"

Terraformで同じことをしたい場合、-backend-configを渡さないといけないため、backendを切り替える際に不便となります。

terraform init -backend-config=./envs/dev/terraform.backend -reconfigure
terraform plan -vars-file=./envs/dev/terraform.tfvars

OpenTofu DayのLTで紹介されてた環境名だけを渡して挙動を切り替えるパターンが現状だとterraformでは使えません

バージョン管理

複数プロジェクトでTerraform or OpenTofuを使う場合、プロジェクトごとに使用バージョンを管理する必要があります。いくつか選択肢を見ていきます。

Terraformのバージョン管理ツールとしてよく使われるtfenvはOpenTofuには対応しません。

代わりにTerraformとOpenTofuに対応したtenvができました。こちらを利用すると良さそうです。

私はTerraformも合わせてプロジェクト内のツールのバージョン管理をまとめてasdfでやってますが、こちらは対応しています。

自分はあまり使わないのですが、同じようなツールのaquaやmiseも両対応しています。

Security check

Terraformだとtfsec(現 trivy config)がセキュリティチェックとして使われてるかと思います。

ディスカッションはされており優先順位をつけて対応するとのことです。

Linter

tflintはOpenTofuをサポートしないようです。

Linterの議論自体はissueで続いているようです。

CI/CD

HCP Terraform(旧Terraform Cloud)に相当するSaaSとしては、OpenTofuスポンサーのSpaceliftenv0Scalrなどがあります。

tfactionsatlantisdiggerもOpenTofuに対応している模様です。

まとめ

現時点でOpenTofuに移行するするべきか?の問については、利用者側として現状では引き続き様子見かと思います。足回りも概ね揃ってきているが、まだ足りないエコシステムもあります。

気になるところではIBM社にHashiCorp社の買収による統合完了の様子も追っていきたいところです。予定では2025年の1-3月期に統合完了するとのことなので、その後なにか動きがあるかもしれません。

とはいえ、1つのツールが使えなくなることで業務が止まるのは避けたいので常に選択肢は複数取っておきたいところです。エンジニアとしてはOpenTofuに限らず、Pulumi、CDK(AWS)なども選択肢として取っておきたいです。

それはそれとして、OpenTofuはTerraformとは違う独自進化をしているので、変更を追っていきたいところです。個人的にはState暗号化とかBackendの変数参照とかTerraformに入ってほしいです。

それでは良い豆腐ライフを!

、、、。

ここまで書いてきたのですが、minamijoyoさんのTerraform職人のためのOpenTofu再入門2024がものすごく詳しいので、この記事以上に参考になるかと思います。

参考リンク

ライセンス変更

フォーク

ソースコード問題

OpenTofuを使うために

HachiCorp買収

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 を用いたコンテナイメージの署名実装と、残された課題について説明しました。

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

参考リンク