1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ改善】SSH22ポートを完全廃止!AWS Session Manager + S3でセキュアなCI/CDデプロイ環境を構築した話

Last updated at Posted at 2025-08-31

はじめに

アプリケーション開発において、セキュリティとデプロイの効率性を両立することは常に課題です。特にSSH22ポートを公開してのデプロイは、ブルートフォース攻撃などのセキュリティリスクが伴います。

また、今回の記事内容はサンプルであり、
私自身がインフラやセキュリティに詳しい訳ではないため
設定やコードを使用する場合は自己責任でお願いします。

本記事では、従来のSSH接続によるデプロイから AWS Session Manager + S3 を活用したセキュアなデプロイ環境への移行について、実際の実装手順から遭遇した問題まで詳しく解説します。

なお、今回のAWS Session Manager + S3でのデプロイ実装は、
自身が作っていた既存のコードをベースにしながら、一定の大きさ以上のファイルをSSH接続を使わずに行いたかったという前提とCodeDeployに変えるには少し大掛かりになりそうだったという前提があります。

そのため、一般的なデプロイ設計としてはAWS CodeDeployなどをお勧めします。

あくまで こういったやり方もできました。 という感じで読んでください。

💡 この記事で得られること

  • SSH22ポート廃止によるセキュリティ向上手法
  • AWS Session Manager + S3を使ったCI/CDパイプライン設計
  • GitHub ActionsとAWSサービスの連携実装
  • 実際に遭遇した問題とその解決方法
  • セキュリティとコストのバランス取り

🎯 背景と課題

従来の構成とリスク

開発スピードを重視した初期段階のプロジェクトや、レガシーシステムでは、SSH接続によるデプロイフローが採用されていることがあります。以下はその典型的な構成例です。

# 従来のSSH方式
- name: Deploy with Rsync
  uses: burnett01/rsync-deployments@5.1
  with:
    switches: '-avz --delete'
    path: build/libs/*.jar
    remote_path: /srv/app/
    remote_host: ${{ secrets.HOST }}
    remote_user: ${{ secrets.USER }}
    remote_key: ${{ secrets.SSH_PRIVATE_KEY }}

- name: Restart application
  uses: appleboy/ssh-action@v0.1.5
  with:
    host: ${{ secrets.HOST }}
    username: ${{ secrets.USER }}
    key: ${{ secrets.SSH_PRIVATE_KEY }}
    script: |
      sudo systemctl restart app-service

主な問題点

  1. SSH22ポート公開:ブルートフォース攻撃の標的になりやすい
  2. アクセス制御:IP制限だけでは完全なセキュリティ確保が困難
  3. 監査ログ:SSH接続の詳細な操作履歴が取りづらい

🔍 解決方法の検討

検討した選択肢

方式 メリット デメリット 判定
VPNソリューション 高セキュリティ インフラ管理複雑、コスト高
AWS CodeDeploy AWS標準、高機能 既存変更大、学習コスト高
Session Manager直接転送 シンプル ファイルサイズ制限
Session Manager + S3 セキュア、柔軟性高 実装工数やや増

最終選択:Session Manager + S3

選択理由:

  • SSH22ポートが完全に不要
  • AWS標準サービスのみで構成
  • 既存のSSHデプロイロジックを大きく変更せずに済む
  • セキュリティレベルの大幅向上
  • 監査ログ対応

🏗️ アーキテクチャ設計

システム構成

デプロイフロー

🛠️ 実装手順

Step 1: IAM設定

重要:

  • S3バケット名は全世界で一意である必要があるため、以下の例の deploy-bucket-* は実際には deploy-<組織名>-<プロジェクト名>-* のような一意な名前に変更してください。
  • Session Manager関連のアクション(ssm、ssmmessages、ec2messages)は、その性質上Resource: "*"を指定する必要があるようです。

EC2用IAMロール(例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:UpdateInstanceInformation",
                "ssm:SendCommand",
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel", 
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel",
                "ec2messages:*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:ListBucket",
                "s3:CreateBucket"
            ],
            "Resource": [
                "arn:aws:s3:::deploy-bucket-*",
                "arn:aws:s3:::deploy-bucket-*/*"
            ]
        }
    ]
}

GitHub Actions用IAMユーザー設定(例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:SendCommand",
                "ssm:GetCommandInvocation",
                "ssm:DescribeInstanceInformation"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:CreateBucket",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::deploy-bucket-*",
                "arn:aws:s3:::deploy-bucket-*/*"
            ]
        }
    ]
}

Step 2: GitHub Actions Secrets

AWS_ACCESS_KEY_ID: <IAMユーザーのアクセスキー>
AWS_SECRET_ACCESS_KEY: <IAMユーザーのシークレットキー>
AWS_REGION: <リージョン名>
EC2_INSTANCE_ID: i-xxxxxxxxxxxxxxxxx
EC2_USER: <EC2ユーザー名>

Step 3: GitHub Actionsワークフロー

Java/Spring Boot アプリケーション例

name: Deploy Application

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      # ビルド処理
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Java
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Build with Gradle
        run: ./gradlew clean build

      # AWS認証設定
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # S3アップロード
      - name: Upload to S3
        run: |
          JAR_FILE=$(ls build/libs/*.jar | head -1)
          JAR_NAME=$(basename "$JAR_FILE")
          TIMESTAMP=$(date +%Y%m%d-%H%M%S)
          # プロジェクト固有の名前を使用(例:組織名-プロジェクト名-リージョン)
          S3_BUCKET="deploy-myorg-myapp-${{ secrets.AWS_REGION }}"
          S3_KEY="deploys/${TIMESTAMP}/${JAR_NAME}"
          
          # S3バケット作成(存在しない場合)
          aws s3 mb "s3://${S3_BUCKET}" --region ${{ secrets.AWS_REGION }} 2>/dev/null || true
          
          # JARファイルをアップロード
          aws s3 cp "$JAR_FILE" "s3://${S3_BUCKET}/${S3_KEY}" --region ${{ secrets.AWS_REGION }}
          
          echo "S3_BUCKET=${S3_BUCKET}" >> $GITHUB_ENV
          echo "S3_KEY=${S3_KEY}" >> $GITHUB_ENV

      # Session Manager経由でデプロイ
      - name: Deploy via Session Manager
        run: |
          COMMAND_ID=$(aws ssm send-command \
            --instance-ids ${{ secrets.EC2_INSTANCE_ID }} \
            --document-name "AWS-RunShellScript" \
            --parameters "commands=[
              \"sudo systemctl stop app-service\",
              \"aws s3 cp s3://${{ env.S3_BUCKET }}/${{ env.S3_KEY }} /tmp/app.jar\",
              \"sudo mv /tmp/app.jar /srv/app/\",
              \"sudo chmod 755 /srv/app/app.jar\",
              \"sudo chown ${{ secrets.EC2_USER }}:${{ secrets.EC2_USER }} /srv/app/app.jar\"
            ]" \
            --region ${{ secrets.AWS_REGION }} \
            --output text --query "Command.CommandId")
          
          # コマンド実行完了を待機
          aws ssm wait command-executed \
            --command-id $COMMAND_ID \
            --instance-id ${{ secrets.EC2_INSTANCE_ID }} \
            --region ${{ secrets.AWS_REGION }}
          
          # 実行結果確認
          STATUS=$(aws ssm get-command-invocation \
            --command-id $COMMAND_ID \
            --instance-id ${{ secrets.EC2_INSTANCE_ID }} \
            --region ${{ secrets.AWS_REGION }} \
            --output text --query "Status")
          
          if [ "$STATUS" != "Success" ]; then
            echo "Deployment failed with status: $STATUS"
            exit 1
          fi

      # アプリケーション再起動
      - name: Restart application
        run: |
          aws ssm send-command \
            --instance-ids ${{ secrets.EC2_INSTANCE_ID }} \
            --document-name "AWS-RunShellScript" \
            --parameters 'commands=["sudo systemctl start app-service"]' \
            --region ${{ secrets.AWS_REGION }}

      # S3クリーンアップ
      - name: Clean up old files
        run: |
          aws s3 rm "s3://${{ env.S3_BUCKET }}/deploys/" \
            --recursive \
            --exclude "*" \
            --include "deploys/*" \
            --exclude "deploys/$(date -d '7 days ago' +%Y%m%d)*" || true

Step 4: EC2環境準備

# AWS CLI v2インストール(Ubuntu/Debian系の例)
sudo apt update
sudo apt install -y unzip curl

# AWS CLI v2のダウンロードとインストール
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# インストール確認
aws --version
# 出力例: aws-cli/2.x.x Python/3.x.x Linux/x.x.x

注意: Amazon Linux 2やRHEL系の場合は、パッケージマネージャーが異なるため適宜調整してください。

🚨 実装時の問題と解決

問題1: AWS CLI not found

症状:

StandardErrorContent: "aws: not found"

原因: EC2にAWS CLIがインストールされていない

解決: EC2に事前にAWS CLI v2をインストール

問題2: s3:GetObject権限不足

症状: ファイルが更新されない

原因: EC2のIAMロールにs3:GetObject権限がなかった

解決: IAMロールに適切なS3権限を追加

問題3: ファイルサイズ制限(初期検討時)

症状:

Argument list too long

原因: Session Manager経由でのBase64転送でコマンドライン制限

解決: S3を中間ストレージとして活用

📊 移行前後の比較

アーキテクチャ比較

詳細比較表

項目 SSH方式 Session Manager + S3方式
セキュリティ SSH22ポート公開必要 ポート公開不要
認証方式 SSH鍵 IAM
監査ログ 限定的 CloudTrail対応
攻撃面 SSH22ポート なし
運用負荷 SSH鍵管理必要 不要
コスト 0円 数円〜数十円/月(S3使用料)
実行時間 ~1-2分 ~1-2分(変化なし)

💰 コスト分析

事前のコスト試算

移行前に S3 使用料金を試算した結果、以下のような想定となりました。

  • バックエンドアプリ: ~50-100MB × 30回/月 = ~1.5-3GB
  • フロントエンドアプリ: ~10-20MB × 30回/月 = ~300-600MB
  • 合計転送量: ~2-4GB/月
  • 想定コスト: 数円〜数十円/月(東京リージョンの場合)

この試算結果から、セキュリティ向上のメリットに対してコスト増加は許容範囲内と判断し、実装に踏み切りました。

結論: 事前試算通り、セキュリティを大幅に向上させながらコスト増加は最小限に抑えられることを確認

🎯 得られた成果

セキュリティ向上

  • ✅ SSH22ポート完全廃止
  • ✅ IAMベース認証システム
  • ✅ AWS標準の暗号化通信
  • ✅ 監査ログ対応(CloudTrail)

運用効率化

  • ✅ SSH鍵管理からの解放
  • ✅ IP制限設定不要
  • ✅ 自動化されたデプロイフロー維持
  • ✅ 緊急時の復旧体制

開発体験

  • ✅ デプロイ手順の変更なし(開発者視点)
  • ✅ 既存ワークフローとの互換性
  • ✅ トラブルシューティングの容易さ

🔧 トラブルシューティング

デバッグ方法

# Session Manager実行結果の詳細確認
aws ssm get-command-invocation \
  --command-id <COMMAND_ID> \
  --instance-id <INSTANCE_ID> \
  --region <REGION> \
  --output json

# S3ファイル確認
aws s3 ls s3://deploy-bucket-<region>/deploys/ --recursive

# EC2ファイル更新確認
ls -la /srv/app/app.jar

よくある問題

  1. Status: FailedStandardErrorContentを確認
  2. aws: not found → EC2にAWS CLIをインストール
  3. Permission denied → IAM権限を確認
  4. File not updateds3:GetObject権限を確認

📚 学んだベストプラクティス

  1. 段階的移行:一気に変更せず、バックアップを保持しながら段階的に
  2. AWS標準サービス活用:独自ソリューションより信頼性の高い標準サービス
  3. 詳細ログ:トラブルシューティングのために十分なログ出力
  4. 権限最小化:必要最小限のIAM権限設定
  5. コスト意識:セキュリティ向上とコストのバランス

まとめ

SSH22ポートの廃止は、一見複雑に思えるかもしれませんが、AWS Session Manager + S3を活用することで、セキュリティを大幅に向上させながらも運用負荷を軽減できました。

主な成果:

  • セキュリティリスクの大幅削減
  • 運用負荷の軽減(SSH鍵管理不要)
  • コスト増加の最小化(月5円程度)
  • 開発体験の維持

特に、一定以上のセキュリティ要件を満たしながら、開発者の日常業務に影響を与えない点は重要なポイントです。

同様の課題を抱えている方の参考になれば幸いです。実装時の詳細な技術的質問があれば、コメントでお気軽にお聞きください!

参考リンク


この記事は実際のプロダクション環境での移行経験をもとに書かれています。環境や要件に応じて適宜調整してご利用ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?