RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

ECS + SSM で実現するセキュアなワンタイム踏み台サーバー

概要

こんにちは、RevCommでインフラを担当している齊藤です。

ECS Fargate を踏み台にする構成自体は、すでに多くの知見があります。 本記事ではそこからさらにもう一歩踏み込み、ABAC を用いた「適切な人に、適切な時だけ」アクセスを許可する仕組みについて紹介します。

単なるサーバーのコンテナ化に留まらず、IdP の属性を活用した本人認証の強化や、Slack 連携によるオンデマンドな環境提供を組み合わせることで、「必要な時に、権限を持つ本人のみが利用できる」という、実運用に即したアクセス管理をいかに実現したかについて共有できればと思います。

背景

当初は「昔ながら」の EC2 + SSH で運用していました。しかし、鍵の管理やセキュリティグループの運用負荷が高かったため、まずは EC2 + SSM Session Manager への移行を行いました。

これにより、踏み台をパブリックサブネットに置く必要がなくなり、SSH 鍵の運用からも解放されました。

しかし、EC2 + SSM に移行したことで利便性は向上しましたが、以下の3つの課題は残りました。

  • 脆弱性対応
    • AWS Inspector を確認すると、どうしても Affected Resources(影響を受けているリソース)として踏み台 EC2 がリストアップされます。
  • 常時起動によるセキュリティリスク
    • 踏み台が常に稼働していることは、潜在的な侵入経路を晒し続けることを意味します。必要な時以外は「存在しない」状態が理想でした。
  • 権限管理の粗さ
    • IAM Role に権限があれば、その Role を持つ全員がログインできてしまいます。「特定の人が、その作業のためにだけ使う」というアイデンティティに基づいた制御には限界がありました。

この課題を解決するために今回紹介する構成に変更することにしました

解決策

  • OS 管理からの解放(脆弱性対応の自動化)
    • 踏み台を EC2 から ECS Fargate に変更しました。
    • コンテナイメージのベースとなる OS パッチは AWS 側で更新されるため、これまでのEC2 の OS メンテナンスからは解放されます。
    • イメージを定期的にビルド・更新することで、脆弱性が Unresolved な状態で放置されるリスクを構造的に抑え込んでいます。
  • オンデマンド起動による攻撃の最小化
    • Slack Workflow + Amazon Q を活用し、必要な時だけ ECS タスクを起動する仕組みを構築しました。
    • 踏み台が「常に存在している」状態をなくすことで、侵入経路としてのリスクを最小限に抑えています。
  • ABAC による「本人限定」のアクセス制御
    • 単なる IAM Role による制御だけでなく、ABAC を導入しました。
    • 起動した本人しかログインできない制約と、IdP 側の属性情報に基づいた動的な権限チェックを組み合わせ、ログイン権限を管理しています。

システム構成

システム概要

システム概要

今回の仕組みは、大きく分けて「起動トリガー」「実行基盤」「アクセス制御」の3つの要素で構成されています。

起動トリガー:Slack Workflow + Amazon Q

Slack Workflow リクエスト画面 Slack Workflow リクエスト画面 Slack Workflow から Amazon Q へのリクエストサンプル

@Amazon Q sns publish --topic-arn arn:aws:sns:ap-northeast-1:***:serverless-bastion-endpoint --chatbot-replace-curly-quotes enable --region ap-northeast-1 --message "{\"env\": \"dev\", \"timer\": \"60\",\"action\":\"start\",\"task\":\"base\",\"user\":\"@xxx\"}"

開発者が使い慣れた Slack をインターフェースにしています。

  • Slack Workflow
    • ユーザーが踏み台を起動したい環境、コンテナイメージなどを選択し起動リクエスト出します
  • Amazon Q
    • Slack Workflow から送信されたパラメータを受け取りリクエストに合わせたコンテナを起動します

実行基盤:ECS Fargate + SSM Session Manager

実際の作業環境は、サーバーレスなコンテナとして提供されます。ここで重要なのが、「使い終わったら自動で消える」ための独自の実装です。

  • 自動終了ロジックの組み込み
    • コンテナ内の初期化処理(Entrypoint または Init プロセス)に、セッションと起動時間を監視するスクリプトを仕込んでいます。
      • アイドル監視
        • SSM Session Manager のプロセスが一定時間無いことを検知すると、タスクを自ら終了させます。
      • タイムアウト監視
        • 万が一セッションが残ったままでも、指定した起動時間を経過すると強制的にタスクを終了するようにしています。
  • EFS マウントによるデータの永続化
    • コンテナ自体は使い捨てですが、/mnt/data などのディレクトリには Amazon EFS をマウントしています。
  • Public IP 不用
    • SSM Session Manager を使用するため、コンテナをプライベートサブネットに配置でき、セキュリティグループでインバウンドを空ける必要もありません。
  • ログの自動取得
    • 誰がどのような操作を行ったかは、SSM の機能によって S3 に自動的に集約されます。

アクセス制御:ABAC による本人認証

Condition 句を使って、「起動した本人か」と「IdP の属性」をチェックしている IAM ポリシーのサンプル

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "StartSessionSubject",
      "Action": "ssm:StartSession",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalTag/Subject": [
            "Team_A",
            "Team_B",
            "..."
          ]
        }
      },
      "Effect": "Deny",
      "Resource": [
        "arn:aws:ecs:ap-northeast-1:***:task/serverless-bastion/*"
      ]
    },
    {
      "Sid": "ServerlessBastionSession",
      "Action": "ssm:StartSession",
      "Condition": {
        "StringNotEquals": {
          "ssm:resourceTag/SessionName": [
            "${aws:PrincipalTag/SessionName}"
          ]
        }
      },
      "Effect": "Deny",
      "Resource": [
        "arn:aws:ecs:ap-northeast-1:***:task/serverless-bastion/*"
      ]
    }
  ]
}
  • 起動者情報の付与
    • ECS タスクの起動時に、タグを利用して「誰が起動したか」という情報をコンテナに関連付けます。
  • 動的なポリシー判定
    • SSM でコンテナに接続しようとする際、IAM ポリシーが以下の2点をチェックします。
      1. Session 接続を試みているユーザーと、コンテナのタグに刻まれた起動者が一致しているか。
      2. IdP から渡された属性情報が、アクセスを許可された条件を満たしているか。

これにより、たとえコンテナを起動できても、権限や属性が正しくないユーザーはログインすらできないという二重のガードレールが機能します

運用してみて工夫した点など

  • 移行の推進
    • 慣れた EC2 環境から移行するのは運用の大幅変更になるため猶予期間を設けて移行を促すようにした
  • CPU アーキテクチャの混在
    • 当初コンテナイメージは ARM しか用意していなかったが、x86_64 前提で構築されているツールがあったため、現在は ARM, x86_64 の両方のアーキテクチャに対応するようにした
  • ECS 上での「docker run」の再現
    • 踏み台上で直接コンテナを実行していた従来の運用を維持するため、「ECS タスクから別の ECS タスクを起動するコマンド」を自作して提供しました
  • 起動時間の短縮(SOCI Index の導入)
    • オンデマンド起動の宿命ですが、タスクのプロビジョニングに 1〜2 分ほどかかり、これに対しては SOCI (Seekable OCI) Index を導入し、イメージの遅延読み込みを有効にすることで、起動時間を短縮する改善を行いました。
  • ABAC 実装のハマりどころ(SessionName への Email 埋め込み)
    • 本人特定のために Slack アカウントの Email を使用しましたが、aws:PrincipalTag/SessionName に動的に Email を含める方法に苦戦しました。最終的には AWS サポートの助けも借りながら、ログインユーザーを正確に識別・制限できるロジックを完成させることができました。

今後の課題

現在は AWS アカウントごとにこの仕組みを展開していますが、セットアップの自動化がまだ完全ではなく、横展開の手間が課題として残っています

最後に

脆弱性対応をやりたくないという、正直な動機から始まった改善でしたが、結果として SOCI や ABAC といった技術的な深掘りに繋がり、以前よりもセキュアな踏み台環境を構築することができたと思います