AWS System Managerセッションマネージャーでポートフォワードする

AWS System Managerセッションマネージャーを利用し、SSM エージェントのインストール先インスタンスからローカルに対してポートフォワードする手順を紹介します。

ユースケース

リモートの Windows サーバーにリモートデスクトップ(RDP)でアクセスするケースを考えます。

本機能を利用することで、以下のようなメリットがあります。

  • リモート VPC のセキュリティグループで RDP ポートを開けなくてよい
  • RDP ポート(3389)のアウトバウンド通信が許可されていなくても、セッションマネージャーが利用するHTTPS(443) ポートだけで RDP 可能
  • プライベートサブネットのサーバーに対して、踏み台を経由せずに直接 RDP 可能
  • リモートサーバーに SSH をインストールしなくてよい

注意点として、ポートフォワード出来るのは、リモートサーバー内で LISTEN しているポートのみです(SSH ポートフォワードで言うところの$ ssh -L 9999:localhost:80 user@SERVER)。

リモートサーバーからアクセス可能な別サーバーのポートはフォワードできないことにご注意ください(SSH ポートフォワードで言うところの $ ssh -L 9999:MySQL:3306 user@SERVER)。例えば、EC2を踏み台にして、RDS に接続することはできません。

そのようなケースに対応するには SSH 版 Systems Manager セッションマネージャーの利用を検討ください。

AWS Systems Manager セッションマネージャーでSSH・SCPできるようになりました

コマンド体系

端的に言えば、AWS System Managerセッションマネージャー接続時と同じコマンド体系です。

違いは、ポートフォワード用ドキュメント(AWS-StartPortForwardingSession)を指定し、リモート・ローカルのポートを指定する引数が増えただけだけです。

System Managerセッションマネージャー接続コマンド

$ aws ssm start-session --target i-123

System Managerセッションマネージャーポートフォワードコマンド

$ aws ssm start-session --target i-123 \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["80"],"localPortNumber":["8000"]}'

セッションマネージャーの利用環境が整っていない場合、まずは次のドキュメントを元に環境を整えてください。

Getting Started with Session Manager - AWS Systems Manager

やってみた

リモートサーバーの 80 番ポートで起動する nginx にポートフォワードしてみます。

1. System Managerエージェントのアップデート

本機能を利用するには、System Managerエージェントのバージョンが 2.3.672.0 以上である必要があります。 これよりも古い場合は、エージェントを最新版にアップデートしてください。

SSM を利用し、ドキュメント AWS-UpdateSSMAgent を RunCommand すればアップデートされます。

2. Session Managerプラグインのアップデート

本機能を利用するには、Session Managerプラグインのバージョンが 1.1.26.0 以上である必要があります。

これよりも古い場合は、プラグインを最新版にアップデートしてください。

# アップデート
$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
$ unzip sessionmanager-bundle.zip
$ sudo ./sessionmanager-bundle/install \
  -i /usr/local/sessionmanagerplugin \
  -b /usr/local/bin/session-manager-plugin
 
# バージョンチェック
$ session-manager-plugin --version
1.1.31.0

3. ポートフォワード

セッションマネージャーでポートフォワードするには

ポートフォワード用ドキュメント(AWS-StartPortForwardingSession)を指定し、リモート・ローカルのポートを指定するだけです。

$ aws ssm start-session --target $INSTANCE_ID \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["80"],"localPortNumber":["8000"]}'

Starting session with SessionId: botocore-session-1569008417-05521c0a55edb2d55
Port 8000 opened for sessionId botocore-session-1569008417-05521c0a55edb2d55.

localPortNumber を省略すると、自動で空いているポートが割り振られます。

$ aws ssm start-session --target $INSTANCE_ID \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["80"]}'

Starting session with SessionId: botocore-session-1569064990-098c3a6376b82743c
Port 51959 opened for sessionId botocore-session-1569064990-098c3a6376b82743c.

このケースだと51959 です。

リモートのポート番号(portNumber)を省略すると、80番ポートが利用されます。

4. 接続確認

別ターミナルで、ローカルポートに対してアクセスしてみます。

$ curl -I localhost:51959
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Sat, 21 Sep 2019 11:23:27 GMT
Content-Type: text/html
Content-Length: 3520
Last-Modified: Wed, 28 Aug 2019 19:52:13 GMT
Connection: keep-alive
ETag: "5d66db6d-dc0"
Accept-Ranges: bytes

確かに、ポートフォワードされていますね。

ポートフォワードアクティヴィティを監視

本機能を利用すると、意図的に特定ポートの通信を塞ぐようにネットワーク設計した環境において、利用者がその制約を回避できてしまう可能性があります。

SSMエージェントや素のセッションマネージャーは、運用面のメリットから許容するとして、過度なポートフォワードの利用には監視の目を光らせたい場合は、CloudTrail を有効化し

  • CloudWatch Events を使って StartSession の API 呼び出しをリアルタイム監視
  • Athena を使って CloudTrail ログ から StartSession の API 呼び出しをバッチ監視

してください。

SSM:StartSession すると CloudTrail には以下の様なログが残ります。

{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "...",
        "arn": "arn:aws:sts::1234:assumed-role/...",
        "accountId": "1234",
        "accessKeyId": "...",
        "sessionContext": {
            ...
        }
    },
    "eventTime": "2019-09-21T11:28:18Z",
    "eventSource": "ssm.amazonaws.com",
    "eventName": "StartSession",
    "awsRegion": "eu-central-1",
    "sourceIPAddress": "1.2.3.4",
    ...
    "requestParameters": {
        "target": "i-1234",
        "documentName": "AWS-StartPortForwardingSession",
        "parameters": {
            "localPortNumber": [
                "8000"
            ],
            "portNumber": [
                "80"
            ]
    },
    "responseElements": {
        ...
    },
    "requestID": "...",
    "eventID": "...",
    "resources": [
        {
            "accountId": "1234",
            "ARN": "arn:aws:ec2:eu-central-1:1234:instance/i-1234"
        },
        {
            "accountId": "1234",
            "ARN": "arn:aws:ssm:eu-central-1::document/AWS-StartPortForwardingSession"
        }
    ],
    "eventType": "AwsApiCall",
    "recipientAccountId": "1234"
}

ドキュメント AWS-StartPortForwardingSession の仕様を確認

  • PortNumber(フォワードされるリモートサーバーのポート)
  • localPortNumber(フォワード先のローカルサーバーのポート)

が定義されているだけです。

{
  "schemaVersion": "1.0",
  "description": "Document to start port forwarding session over Session Manager",
  "sessionType": "Port",
  "parameters": {
    "portNumber": {
      "type": "String",
      "description": "(Optional) Port number of the server on the instance",
      "allowedPattern": "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$",
      "default": "80"
    },
    "localPortNumber": {
      "type": "String",
      "description": "(Optional) Port number on local machine to forward traffic to. An open port is chosen at run-time if not provided",
      "allowedPattern": "^([0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$",
      "default": "0"
    }
  },
  "properties": {
    "portNumber": "{{ portNumber }}",
    "type": "LocalPortForwarding",
    "localPortNumber": "{{ localPortNumber }}"
  }
}

最後に

AWS Systems Manager セッションマネージャーのポートフォワード機能を紹介しました。

SSH の複雑なオプションと格闘することなく、だれでも簡単にポートフォワードできます。

現時点ではシンプルなポートフォワードにしか対応していませんが、今後のアップデートにより、より多くの SSH ポートフォワードのケースを置き換えられるようになるのではないでしょか。

それでは。

参考