AWS Sesstion Manager で Fargate に SSH 接続する

# AWS Session Manager

AWS Session Manager は AWS Systems Manager の機能で、AWS CLI を経由して EC2 インスタンス、オンプレミスインスタンス、仮想マシン (VM) にログインできるようにしてるというものです。

IAMベースで認証するのでセキュリティーグループを神経質に設定してインバウンドポートを開いたり、パブリックIPアドレスを開放する必要がなくなるというメリットがあります。

また、ハイブリットアクティベーション機能を利用することで Fargate のようなマネージドインスタンスに SSM ログインしターミナルセッションを開くことができます。面倒な SSH 鍵の登録などは不要です。

# とはいえSSHしたいときもある

とはいえ、運用の都合でSSHしたいこともあります。ということで、AWS Sesstion Manager を経由してFargate コンテナへのSSH 接続を試してみました。

# Dockerコンテナの準備

まず大前提として Fargate に SSH するにはデプロイするコンテナに SSH できる必要があります。それに加えて、SSMエージェントをインストールインストールしハイブリットアクティベーション可能なコンテナを作ります。

```Dockerfile
FROM ubuntu:18.04

COPY run.sh /run.sh

RUN apt-get update \
    && apt-get -y install curl openssh-server \
    && curl https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/amazon-ssm-agent.deb \
    && dpkg -i /tmp/amazon-ssm-agent.deb \
    && cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml

RUN mkdir /var/run/sshd
RUN sed -i 's/#\?PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile

COPY path/to/id_rsa.pub /root/authorized_keys

RUN mkdir ~/.ssh && \
    mv ~/authorized_keys ~/.ssh/authorized_keys && \
    chmod 0600 ~/.ssh/authorized_keys

EXPOSE 22

CMD ["bash", "run.sh"]
```

```sh:run.sh
#!/bin/bash

/usr/sbin/sshd &

amazon-ssm-agent -register -code "${SSM_AGENT_CODE}" -id "${SSM_AGENT_ID}" -region "${AWS_DEFAULT_REGION}"
amazon-ssm-agent
```

作成した Docker Image は ECR に登録して利用しますが詳細は端折ってます。

# ハイブリットアクティベーションのセットアップ

## ハイブリッド環境の IAM サービスロールを作成する

作成するハイブリットアクティベーションにアタッチするロールを作成します。インスタンスと System Manager の通信を許可する設定が入ってます。

```json:ssm-trust.json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Principal": {"Service": "ssm.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }
}
```
```bash
aws iam create-role \
    --role-name SSMServiceRole \
    --assume-role-policy-document file://ssm-trust.json 

aws iam attach-role-policy \
    --role-name SSMServiceRole \
    --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
```

## SSM アクティベーションを作成する

```sh
aws ssm create-activation --description my-fargate-task \
  --iam-role "SSMServiceRole" \
  --registration-limit 1000 \
  --default-instance-name my-fargate-task
{
    "ActivationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ActivationCode": "yyyyyyyyyyyyyyyyyyyy"
}
```

<img width="752" alt="スクリーンショット 2020-05-24 17.39.16.png (106.6 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/25/41057/112ff704-c600-4f02-a5d8-c810276448c1.png">

Activation ID, Code は後の工程で利用するのでメモしておきます。

## パラメータストアに登録

作成した ActivationId, ActivationCode をパラメータストアに登録します。これらの情報は Fargate Task 起動時に読み込まれます。

```sh
aws ssm put-parameter --overwrite --name "my-activation-id" \
  --value "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  --type "SecureString"

aws ssm put-parameter --overwrite --name "my-activation-code" \
  --value "yyyyyyyyyyyyyyyyyyyy" \
  --type "SecureString"
```

<img width="702" alt="スクリーンショット 2020-05-24 17.40.35.png (124.5 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/25/41057/7f98dd9c-4057-4038-a090-6dba67dc9c42.png">

<img width="723" alt="スクリーンショット 2020-05-24 17.40.05.png (128.5 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/25/41057/17321d6e-6dec-4087-b416-10764a35a044.png">

# Task Definition 作成

起動タイプに Fargate を選択して、コンテナの定義を下記の通り設定します。

## 環境変数

Task Definition 起動時に ssm-agent を起動するのでパラメータストアから先程登録した ActivationId, ActivationCode を環境変数に読み込ませます。

<img width="758" alt="スクリーンショット 2020-05-24 16.28.46.png (88.1 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/bb24f522-a830-404b-b71a-a63e04dbbabb.png">


## ポートマッピング

SSHログインするのでポートは 22 をマッピングしておきます。

<img width="555" alt="スクリーンショット 2020-05-24 15.36.31.png (33.4 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/244a41ff-ffe1-4b81-a2af-84f9e8957019.png">

# Fargate Task 起動

## Task Role に SSMの読み込みポリシーをアタッチ

タスク起動時にパラメータストアから ssm-agent 起動のための値を読み込む必要があるので、以下のようなポリシーを作成し、 `ecsTaskExecutionRole` 等にアタッチしておきます。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DescribeSSMParameter",
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameters",
        "secretsmanager:GetSecretValue",
        "kms:Decrypt"
      ],
      "Resource": "*"
    }
  ]
}
```

## Fargate Task 起動

Fargate Task を起動します。

<img width="770" alt="スクリーンショット 2020-05-24 16.40.00.png (202.2 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/25/41057/10808189-4951-4075-b6d5-cdb2090e37b7.png">


起動していることが確認できました。

<img width="641" alt="スクリーンショット 2020-05-24 17.34.08.png (161.6 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/c7717c55-4bdc-4003-b09c-aaa37c811c1d.png">


セキュリティグループは インバウンドトラフィックはVPC内部での通信のみ許可、アウトバウンドはVPC内部のトラフィックに加えて 443 ポートのトラフィックを許可しました。

また、ECR からの Image 取得、Fargate と System Manager との通信許可のため Private Link を設定しています。

<img width="1207" alt="スクリーンショット 2020-05-24 21.25.58.png (270.8 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/ee63fae5-97f2-43ac-b7ba-8a0754db8e3a.png">

Cloud Watch からログを確認してみると ssm-agent の起動が確認できます。`mi-*` となっているのがインスタンスIDで 、後に SSM および SSH ログインする際に利用します。

<img width="836" alt="スクリーンショット 2020-05-24 17.27.42.png (381.9 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/fb8305a8-c577-4983-99b7-944c2ea8f2b7.png">

また、`aws ssm describe-instance-information` で ssm-agent が起動しているインスタンスの情報を閲覧することができます。

```sh
aws ssm describe-instance-information
{
    "InstanceInformationList": [
        {
            "InstanceId": "mi-0abfaba2ca9885629",
            "PingStatus": "Online",
            "LastPingDateTime": "2020-05-24T17:55:55.927000+09:00",
            "AgentVersion": "2.3.978.0",
            "IsLatestVersion": false,
            "PlatformType": "Linux",
            "PlatformName": "Ubuntu",
            "PlatformVersion": "18.04",
            "ActivationId": "63dd7755-c64b-477c-b2ad-25e46a964363",
            "IamRole": "SSMServiceRole",
            "RegistrationDate": "2020-05-24T17:23:55.481000+09:00",
            "ResourceType": "ManagedInstance",
            "Name": "my-fargate-task",
            "IPAddress": "169.254.172.42",
            "ComputerName": "ip-10-0-1-86.ap-northeast-1.compute.internal"
        }
    ]
}
```

# SSMログインする

SSMログインできるか試してみます。

Fargaet にSSMログインするには *System Manager → マネージドインスタンス → 設定 → インスタンス枠* が 「高度なインスタンス枠を使用する」設定になっている必要があるので事前に設定を変更しておきます。

<img width="989" alt="スクリーンショット 2020-05-24 21.13.25.png (84.2 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/05/24/41057/f6fdee49-341d-49e4-8e31-60606d3188ba.png">

以下のようにして SSM ログインすることができます。

```sh
aws ssm start-session --target mi-0abfaba2ca9885629

Starting session with SessionId: me-0c78ed24d7770a19c
$ whoami
ssm-user
```

# SSHログインする

ssh_config を修正します。proxy command で start-session コマンドを経由して SSH するような設定になっています。

```
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
    IdentityFile path/to/id_rsa
    UseKeychain yes
    AddKeysToAgent yes
```

SSH 接続してみましょう。

```sh
ssh root@mi-0abfaba2ca9885629
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.14.158-129.185.amzn2.x86_64 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Sun May 24 15:10:15 2020 from 127.0.0.1

root@ip-10-0-1-86:~#
```

Sesstion Manager を使って Fargate に SSH できるようになりました。

# 所感

そもそも Session Manager を使うと IAM で認証できるから、SSH する必要もなくて SSH 鍵の管理もしなくていいというのがメリットなはずなのに、開発してると結局 SSH しなきゃいけないタイミングが出てきてしまうのがモヤッとしてます。

System Manager 側で SSH をサポートするもっと便利な機能を提供してくれないかなーと感じています。

とはいうものの Public なインスタンスを作らなくて良いのはセキュリティの面ではメリットではないかと思います。

# 参考

- [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html)
- [ハイブリッド環境の IAM サービスロールを作成する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-service-role.html)
- [Fargate containerにSession Managerでログインする](https://enokawa.hatenablog.jp/entry/2019/09/05/104545)
- [AWS FargateでNATを利用する時の注意事項](https://qiita.com/kutsuna_m/items/54f17b2c83312316b700)
- [DockerイメージをECRからプライベートな接続で取得するにはPrivate Linkを使うべし](https://qiita.com/blackriver/items/9389f36420ebaa0390bd)