EMoshU Blog
2024/02/01
2024-02-01
Ken

GitHub ActionsでAWS EC2環境へ鍵なしでアクセスする

GitHub Actionsを用いてAWS EC2でコマンドを実行する方法についてまとめました。EC2上のサーバーで特定の処理をしたい場合に参考になればと思います。

もくじ

 

 

はじめに

EMoshUでサーバーサイドエンジニアをしているけんです。

今回はGitHub Actionsを用いてサーバーアクセスを自動化することで、鍵やインスタンス情報の管理をなくす取り組みについてお伝えします。

ゴールは下図のような構成でユーザーがGitHub Actions経由で各サーバーにコマンドを送る形です。

kousei.jpg

 

 

GitHub Actions

ご存知の方も多いと思いますが改めてGitHub Actionsについてご紹介します。

GitHub ActionsとはGitHubが提供するCI/CDのためのサービスで、GitHub上でのアクションをトリガとして様々な事柄を自動化するために用いられています。

GitHub Freeでも月2000分の利用時間が提供されていることや、コード管理でのGitHub利用者が多いことから、活用している方も非常に多いサービスだと思います。

GitHub Actionsの用途としてはコードのビルドやデプロイ、静的解析などによく使われており、pushだったりpull requestの作成だったりと、git上での行動に紐づいてワークフローを自動実行することが可能です。

 

そんなGitHub Actionsをセキュリティアップデートを自動化する目的で、AWS EC2のサーバーへアクセスし、コマンドを実行する用途で使ってみました。

また、今回は作業者のタイミングでアクションを実行したいため、git上でのアクションに紐づける形ではなく、GitHub上のUIからパラメータを指定して手動で実行する方法を採用しました。

 

 

サーバーアクセス自動化の必要性

EMoshUでは開発や運用のため複数のサーバーを管理しており、定期的に各サーバーにアクセスしてセキュリティアップデートを実施する必要があります。

セキュリティアップデートの自動化としてはyum-cronを用いた方法もありますが、アップデートによる影響にいち早く気づけるよう、作業者が監視しながらアップデートを実施する方法を取っています。

各環境にsshしてセキュリティアップデートコマンドを実行する部分はシェルスクリプトとして自動化されていますが、環境へアクセスするためのキーの管理やターゲットの環境情報などを各作業者で管理する形を取っており、特に新メンバーに作業を引き継ぐ際に手間が発生していました。

またこの作業はサーバーサイドエンジニアが持ち回りで担当しているのですが、回ってくる頻度が少ないため、毎回手順書を見ながらバッチを手動実行したり、各環境へアクセス、コマンドを実行したりする必要がありました。


 

 

自動化のシナリオ

セキュリティアップデートの自動化に当たってはGitHub Actionsの他にJenkinsやCircleCIなどを検討しました。

Jenkinsは保守性の面やJenkins用にサーバーを建てるなどの手間がかかることから断念しました。

GitHub ActionsとCircleCIを比較した結果、開発でGitHubを使用していることやデプロイ自動化などで既にGitHub Actionsを社内活用している例があるため、今後も社内での活用が増えていきそうなGitHub Actionsで自動化を進めることにしました。

 

GitHub Actionsで自動化を進めるために当たって解決する必要のある課題としては大きく3点がありました。

1. どうやって各環境にアクセスしてアップデートを実施するか?

2. 鍵の管理をどうするか?

3. GitHub Actionsのトリガ実行をどうするか?

 

まず各環境へのアクセス方法ですが、現状の各環境へsshする方法では鍵の管理が必要になります。

今回は鍵の管理をなくしたいという要望と、対象サーバーが全てAWS EC2で構築されているという前提から、AWS Systems Manager(SSM)の機能であるRun Commandを使って各サーバーでコマンドを実行し、アップデートを実施することにしました。

 

次はどうやってAWSに対する認証をどう行うかの問題になりますが、こちらはGitHub Actions向けにOIDCを利用して鍵を使用せずに認証することが可能になっています。具体的には以下のアクションを使うことで鍵を管理することなく、IAMロールを指定して、指定したIAMロールの権限の範囲でAWS上のリソースへアクセスできるようになります。

https://github.com/aws-actions/configure-aws-credentials

上記アクションを使用するためにはAWS上でGitHub Actions用のIDプロバイダと、必要な権限を付与したIAMロールを作成する必要があります。

手動でのGitHub Actionsのトリガですが、こちらはworkflow_dispatchを指定することで手動でトリガすることが可能です。

また、実行する環境やコマンドをパラメータとして指定し、アクションを実行したい場面もあるかと思います。

こちらはinputsで指定が可能です。例えば以下のような記述を追加することで、手動トリガで実行が可能で、かつtargetパラメータとしてtarget1かtarget2を指定するアクションが作成できます。

 


on:
  workflow_dispatch:
    inputs:
      target:
        description: 'ターゲット'
        required: true
        type: choice
        options:
          - "target1"
          - "target2"

 

 

実装

AWS側の準備

 

IAMからIDプロバイダの作成を行います。

スクリーンショット 2024-01-11 17.37.02.png

OpenID Connectを選択し、プロバイダのURLにhttps://token.actions.githubusercontent.comを入力後サムプリントを取得、を押下します。

対象者にはsts.amazonaws.comを入力します。プロバイダを追加を押下するとIDプロバイダが作成されます。

スクリーンショット 2024-01-11 17.48.09.png

SSMを使用して各サーバーへのアクセスを行うため、SSMから対象サーバーの管理を可能にするためには、SSM Agentが対象サーバーにインストールされている必要があります。

ただし、一部のAMIにはSSM Agentがプリインストールされています。

SSM AgentがプリインストールされているAMIの一覧は下記のURLから確認できます。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ami-preinstalled-agent.html

また、SSMからの制御を可能にするために、アクセス対象となるインスタンスにAmazonSSMManagedInstanceCoreのポリシーをアタッチします。

 

GitHub Actionsで使用するIAMロールの作成を行います。カスタム信頼ポリシーで以下のように設定する必要があります。

スクリーンショット 2024-01-11 17.58.26.png

 


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::[アカウントID]::oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
                    "token.actions.githubusercontent.com:sub": "repo:[ユーザー名]/[リポジトリ]:ref:refs/heads/main"
                }
            }
        }
    ]
}

 

 

"token.actions.githubusercontent.com:sub"を設定しないと任意のリポジトリからこのロールが使用可能になってしまいますので特に忘れないように注意して下さい。

次にGitHub ActionsでRun Commandの実行に必要なポリシーを設定します。

 


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:SendCommand",
                "ssm:GetCommandInvocation",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:CreateControlChannel"
            ],
            "Resource": [
                "arn:aws:ec2:ap-northeast-1:[アカウントID]:instance/*",
                "arn:aws:ssm:ap-northeast-1::document/AWS-RunShellScript"
            ]
        }
    ]
}

SSMのRun Command(SendCommand, GetCommandInvocation)を用いてコマンドを実行,結果を取得する以外の動作を行う場合は適宜ポリシーを追加するか、別のロールを用意して別のGitHub Actionsとして実行します。

 

 

Actionsの例

 

以下にaws-actions/configure-aws-credentialsとSSM Run Commandを用いて対象サーバーでコマンドを実行するjobの例を挙げます。

今回は実行例を示すためにhostnameを実行する形で記載しています。セキュリティアップデートを行う際はこのコマンド部分を修正する形になります。

また再起動を行なったり、再起動後に正常に稼働しているかのチェックなどの処理を追加で行う形になります。


name: ec2access
on:
  workflow_dispatch:
    inputs:
      target:
        description: 'ターゲット'
        required: true
        type: choice
        options:
          - "target1"
          - "target2"

permissions:
  contents: read
  id-token: write

jobs:
  ec2_access:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
            role-to-assume: arn:aws:iam::[アカウントID]:role/[このアクションで使用するロール]
          aws-region: ap-northeast-1
      - name: set target 1
        if: github.event.inputs.target == 'target1'
        run: echo 'INSTANCE_ID=${{ secrets.TARGET_1 }}' >> $GITHUB_ENV

      - name: set target 2
        if: github.event.inputs.target == 'target2'
        run: echo 'INSTANCE_ID=${{ secrets.TARGET_2 }}' >> $GITHUB_ENV

      - name: do command
        run: |
          export RESPONSE=$(aws ssm send-command \
            --instance-ids $INSTANCE_ID \
            --document-name "AWS-RunShellScript" \
            --comment "test" \
            --parameters commands=hostname \
            --output json)
          export COMMAND_ID=$(echo $RESPONSE | jq .Command.CommandId | sed s/\"//g)
          echo "commandId ${COMMAND_ID}"
          sleep 5
          export STATUS=$(aws ssm  get-command-invocation \
            --command-id ${COMMAND_ID} \
            --instance-id $INSTANCE_ID \
            --query 'Status' | sed s/\"//g)
          echo $STATUS
          if [ ${STATUS} != "Success" ]; then
            exit 1
          fi
          echo $(aws ssm  get-command-invocation \
          --command-id ${COMMAND_ID} \
          --instance-id $INSTANCE_ID \
          --query 'StandardOutputContent')
          exit 0

実行した結果は以下になります。

スクリーンショット 2024-01-12 13.16.39.png

 

まとめ

今回はGitHub Actionsを用いて手動トリガで各サーバーでコマンドを実行するまでの流れを説明しました。

主な目的としては鍵の個人管理をなくして、セキュリティアップデートを実施できるようにすることでしたが、実行するコマンド部分を変更することで他の目的にも活用できるかと思います。

またスケジューリングで実行するようにアクションを指定することで、様々な定期タスクの自動化ができそうです。

 

ここまで読んでいただきありがとうございました。

EMoshUでは一緒に開発できる仲間を募集しております。

ご興味を持たれましたら、ぜひ募集要項をご覧ください。カジュアル面談なども実施しております!

 

参考サイト

https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html

https://zenn.dev/kou_pg_0131/articles/gh-actions-oidc-aws

 

募集要項

 

 

 

 

まずは話を聞いてみたいという方へ