Session Manager と SSH についてまとめています。

SSH over Session Manager

ローカルのシェルから EC2 にログインする際、以下の SSH 設定で Session Manager をプロキシとして使えます。 EC2 との接続に 22 番ポートを使わなくて良い点がメリットになります。

host i-* mi-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"

この方法では Session Manager はあくまでネットワーク経路を置き換えるだけで、SSH 鍵は引き続き必要です。

通常の SSH:
ローカル ---[TCP port 22]---▶ EC2
         └─ SSH 鍵で認証

SSH over Session Manager:
ローカル ---[SSM トンネル]---▶ EC2
                              └─ SSH 鍵で認証(変わらず必要)

接続方式の比較

  通常の SSH SSH over Session Manager Session Manager のみ(start-session)
ネットワーク経路 port 22 直接 SSM トンネル SSM トンネル
SSH 鍵 必要 必要 不要
認証 SSH 鍵 SSH 鍵 IAM ロールのみ
port 22 開放 必要 不要 不要

なぜ SSH over Session Manager を使うか

SSH 鍵が残るにもかかわらず使う理由:

  • SG の port 22 を開けなくてよい(ネットワーク攻撃面の縮小)
  • 踏み台サーバー不要
  • SCP / SFTP / ポートフォワードなど SSH の機能をそのまま使える

長期認証情報の排除の観点

Session Manager のみ(aws ssm start-session)で shell に入る方法であれば SSH 鍵も不要になり、IAM ロールだけで完結します。SSH over Session Manager はセキュリティを改善しつつ SSH の利便性を保つ折衷案という位置づけです。

踏み台サーバーが不要になる理由

踏み台サーバーが必要なのは、EC2 がプライベートサブネットに配置されており外部から TCP で到達できない場合です。

【踏み台サーバーが必要な構成】
インターネット
    │
    ▼
踏み台 EC2(パブリックサブネット、port 22 open)
    │
    ▼
対象 EC2(プライベートサブネット)

Session Manager は SSM Agent(EC2 側)が AWS の SSM エンドポイントに対してアウトバウンド接続を張ります。そのためにインバウンドの port 22 が不要になります。

【Session Manager を使った構成】
EC2(プライベート)─── アウトバウンド HTTPS ───▶ SSM エンドポイント(AWS)
                                                        ▲
ローカル ─────────────────────────────────────────────┘

外から EC2 に入る」のではなく EC2 が外(SSM)に繋ぎに行く構造のため、プライベートサブネットでも踏み台なしで接続できます。

SSM エンドポイントの位置づけ

SSM エンドポイント(ssm.ap-northeast-1.amazonaws.com)は VPC とは独立して AWS が提供するサービスエンドポイントです。

AWS グローバルネットワーク
┌──────────────────────────────────────────────────────┐
│  SSM エンドポイント(ssm.ap-northeast-1.amazonaws.com)│
└──────────────────────────────────────────────────────┘
         ▲
         │ HTTPS(アウトバウンド)
┌────────────────────────┐
│  VPC                   │
│  ┌──────────────────┐  │
│  │ EC2(プライベート)│  │
│  └──────────────────┘  │
└────────────────────────┘

EC2 から SSM エンドポイントへの疎通方法は 2 つあります。

方法 概要
NAT Gateway プライベートサブネット → NAT GW → インターネット → SSM エンドポイント
VPC エンドポイント(PrivateLink) VPC 内に SSM 用のエンドポイントを作り、インターネットを経由しない

EC2 側の前提条件

  • SSM Agent がインストールされ、起動していること(Amazon Linux や Ubuntu の主要 AMI ではプリインストール)
  • インスタンスプロファイルに SSM の権限(例:AWS 管理ポリシー AmazonSSMManagedInstanceCore)が付与されていること
  • SSM への疎通経路(NAT Gateway または VPC エンドポイント)があること

VPC エンドポイントを使う場合、ssm だけでは足りません。Session Manager には以下のエンドポイントが必要です。

エンドポイント 用途
ssm SSM API
ssmmessages Session Manager のセッション通信(データチャネル)
ec2messages 古いバージョンの SSM Agent(3.3.40.0 未満)でのみ必要

Session Manager による接続

GitHub Action

準備

  1. vars.XXX が登録されていること
    • AWS_ACCOUNT_ID:AWS アカウント ID
    • AWS_EC2_INSTANCE:接続先の EC2 インスタンス ID
    • AWS_EC2_AZ:接続先の EC2 の AZ
    • SSH_HOST_KEY:EC2 の SSH ホスト公開鍵
  2. AWS に deployment-role を作成して信頼関係でリポジトリを許可していること
  3. deployment-role に接続に必要な権限を付与していること
  4. EC2 側で EC2 Instance Connect が利用可能で、OS ユーザー github-actions が存在すること(事前に作成しておく)

deployment-role の信頼関係

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam:::oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:sample-org/sample-repo:*"
                }
            }
        }
    ]
}

GitHub の subrepo:<org>/<repo>:... の形式です(sample-org/sample-repo:* のように repo: を付けないと一致しません)。
ワークフローで environment を使っている場合、subrepo:<org>/<repo>:environment:<環境名> になります。可能であれば repo:sample-org/sample-repo:environment:production のように環境まで絞り込んでください。

deployment-role の権限ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ssm:StartSession",
            "Resource": [
                "arn:aws:ec2:ap-northeast-1:123456789012:instance/i-1234567890abcdef0",
                "arn:aws:ssm:ap-northeast-1::document/AWS-StartSSHSession"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "ssm:TerminateSession",
            "Resource": "arn:aws:ssm:*:*:session/${aws:userid}-*"
        },
        {
            "Effect": "Allow",
            "Action": "ec2-instance-connect:SendSSHPublicKey",
            "Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/i-1234567890abcdef0",
            "Condition": {
                "StringEquals": {
                    "ec2:osuser": "github-actions"
                }
            }
        }
    ]
}

123456789012i-1234567890abcdef0 はダミーです。

.github/deployment.yaml

name: Deployment

#...
jobs:
  deployment:
    name: Deployment
    runs-on: ubuntu-22.04
    environment: $
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-duration-seconds: 1800
          # AWS の IAM に同名のロールを作成し、信頼関係でこのリポジトリを許可する。
          # OIDC の場合はロール名だけでは指定できないため、ARN で指定する。
          role-to-assume: arn:aws:iam::$:role/deployment-role
          aws-region: ap-northeast-1

      - name: Setup SSH through AWS SSM
        run: |
          mkdir -p ~/.ssh
          echo "$ ssh-ed25519 $" >> ~/.ssh/known_hosts
          cat >> ~/.ssh/config <<EOT
            Host i-* mi-*
                ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
          EOT
          ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ''
          aws ec2-instance-connect send-ssh-public-key \
              --instance-id $ \
              --instance-os-user github-actions \
              --availability-zone $ \
              --ssh-public-key file://~/.ssh/id_ed25519.pub
          until ssh github-actions@$ exit; do
              echo "Waiting for SSH connection..."
              sleep 3
          done
AWS が GitHub(ワークフロー)を認証
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-duration-seconds: 1800
          role-to-assume: arn:aws:iam::$:role/deployment-role
          aws-region: ap-northeast-1

GitHub が発行した OIDC トークンを AWS(IAM / STS)が検証し、ロールの信頼関係(aud / sub の条件)を満たす場合のみ一時認証情報を発行します。

GitHub が EC2 の正当性を認証

ワークフローが不正なサーバーに接続しないためです。

echo “i-1234567890 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyDataHere1234567890abcdefghijklmnopqrstuvwxyz” » ~/.ssh/known_hosts

AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyDataHere1234567890abcdefghijklmnopqrstuvwxyz は EC2 サーバーの公開鍵(値はダミー)。

known_hosts に登録したホスト鍵と接続先の鍵が一致するかを SSH が検証します。この検証を有効にするため、StrictHostKeyChecking no は設定しません(設定すると known_hosts による検証が無効になり、なりすましを検出できなくなります)。

プラグイン

ローカルのターミナルで Session Manager を使用するには Session Manager プラグイン が必要。
macOS での Session Manager プラグインのインストール