アクセスキー払い出しの承認ワークフローを作ってみた

アクセスキーを払い出す際に承認するか否かの判断を挟むワークフローを SSM オートメーションで構築してみました
2024.06.04

こんにちは、AWS 事業本部の平木です!

皆さんはアクセスキーを適切に管理していますか?

アクセスキーは極力使用しないことが好ましいですが、どうしても使用しないといけないケースがあるのは事実です。

ただしアクセスキーの使用を許容しても、簡単に払い出されては困る。
そんな悩みを解消するためにアクセスキー払い出しの承認ワークフローの構成を考えてみました。

アーキテクチャ

アクセスキーの払い出しに承認ワークフローを設けるために今回は、AWS Systems Manager Automation を活用します。

SSM Automaion を活用することで SSM ドキュメントに記載した内容の自動実行が可能になります。

詳しくはこちらのブログをご参照ください。

実際に今回考えてみたアーキテクチャは以下です。

流れとしては、

  • ① SSM Automation に申請者(requester)からの申請を受け付ける
  • ② 申請内容を承認者(approver)に通知
  • ③ 承認者が承認か拒否かの回答
  • ④ 承認された場合は、後続の Lambda を呼び出し
  • ④' 拒否された場合は、拒否した通知を送付
  • ⑤ 呼び出された Lambda は、申請内容に基づいて対象の IAM ユーザーのアクセスキーを払い出し
  • ⑥ Lambda の処理の中で取得したアクセスキーとシークレットアクセスキーを Secret Manager に保管
  • ⑦ Lambda の処理の中で Secret Manager に保管成功した旨を通知
  • アクセスキーが既に 2 つ払い出されている場合は、払い出しに失敗した旨を通知

今回は最終的な結果を承認者に通知する仕組みとしていますが、申請者毎に SNS トピックを作成したり SES を活用することで申請者に承認または拒否の通知を自動化することができます。

やってみた

構築

今回のアーキテクチャを CloudFormation で作成してみます。

コードは以下です。

AWSTemplateFormatVersion: '2010-09-09'
Description: IAM Access Key Request Automation

Parameters:
  # 承認者の通知先のメールアドレス
  AdminEmail:
    Type: String
    Description: Email address of the administrator

  # 承認用 IAM ユーザー名
  ApprovalUser:
    Type: String
    Description: IAM user to approve access key requests

Resources:
  # Lambda の IAM ロール
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - iam:CreateAccessKey
                  - iam:DeleteAccessKey
                  - iam:GetUser
                  - iam:ListAccessKeys
                  - secretsmanager:CreateSecret
                  - secretsmanager:DeleteSecret
                Resource: '*'
              - Effect: Allow
                Action:
                  - sns:Publish
                Resource: !Ref NotificationTopic

  # アクセスキー処理用 Lambda
  AccessKeyRequestFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import json
          import os
          from datetime import datetime
          def lambda_handler(event, context):
            iam_user = event['iam_user']
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            iam = boto3.client('iam')
            sns = boto3.client('sns')
            # IAM ユーザーが既に 2 つのアクセスキーを持っているかどうかを確認する
            response = iam.list_access_keys(UserName=iam_user)
            access_keys = response['AccessKeyMetadata']
            if len(access_keys) >= 2:
                # 管理者にエラー通知を送信する
                message = f"IAM ユーザ「 {iam_user} 」のアクセスキー発行のリクエストに失敗しました.\r\n アクセスキーが既に 2 つ発行されています."
                sns.publish(
                    TopicArn=os.environ['NOTIFICATION_TOPIC'],
                    Message=message,
                    Subject='Access Key Request Failed'
                )
                return {
                    'status': 'error',
                    'message': 'IAM user already has 2 access keys'
                }
            # IAM ユーザーのアクセスキーを作成する
            response = iam.create_access_key(UserName=iam_user)
            access_key = response['AccessKey']
            # AccessKey オブジェクトから必要な属性を抽出する
            access_key_data = {
              'AccessKeyId': access_key['AccessKeyId'],
              'SecretAccessKey': access_key['SecretAccessKey']
            }
            # アクセスキーデータを階層的な命名規則で Secrets Manager に保存する
            secrets_manager = boto3.client('secretsmanager')
            secret_name = f"{iam_user}/access-key-{timestamp}"
            secrets_manager.create_secret(
              Name=secret_name,
              SecretString=json.dumps(access_key_data)
            )
            # 管理者に承認通知を送信する
            message = f"IAM ユーザ「 {iam_user} 」のアクセスキー発行のリクエストが承認されました.\r\n アクセスキーは Secrets Manager「 {secret_name} 」に保存されました."
            sns.publish(
              TopicArn=os.environ['NOTIFICATION_TOPIC'],
              Message=message,
              Subject='Access Key Created'
            )
            return {
              'status': 'created',
              'secret_name': secret_name
            }
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.12
      Timeout: 60
      Environment:
        Variables:
          NOTIFICATION_TOPIC: !Ref NotificationTopic

  # アクセスキー申請処理用 SSM ドキュメント
  AccessKeyRequestAutomation:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      Content:
        schemaVersion: '0.3'
        description: Automate IAM access key requests
        parameters:
          IamUser:
            type: String
            description: IAM user for which access key is requested
          RequestReason:
            type: String
        mainSteps:
          - name: ApprovalStep
            action: aws:approve
            timeoutSeconds: 86400
            onFailure: Abort
            inputs:
              NotificationArn: !Ref NotificationTopic
              Message: IAM ユーザ「 {{ IamUser }} 」のアクセスキー発行をリクエストします. 申請理由は、「{{ RequestReason }}」
              MinRequiredApprovals: 1
              Approvers:
                - !Sub arn:aws:iam::${AWS::AccountId}:user/${ApprovalUser}
          - name: CreateAccessKey
            action: aws:invokeLambdaFunction
            timeoutSeconds: 300
            maxAttempts: 1
            onFailure: Abort
            inputs:
              FunctionName: !Ref AccessKeyRequestFunction
              Payload: !Sub |
                {
                  "iam_user": "{{IamUser}}"
                }

  # 通知用 SNS トピック
  NotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref AdminEmail
          Protocol: email

CloudFormation デプロイ時に入力が必要なパラメータは以下です。

  • AdminEmail
    • 承認者のメールアドレス
  • ApprovalUser
    • 承認者の IAM ユーザー名(存在しない場合は事前に IAM ユーザーを作成してください)

検証

構築した環境で実際に申請からアクセスキー払い出しまでやってみました。

事前に、承認者(approver)と申請者(requester)の IAM ユーザーを作成済みです。
※承認者にはAmazonSSMAutomationApproverAccess(AWS管理ポリシー)の権限が必要なため、必要に応じてアタッチしてください

まずは、Systems Manager のナビゲーションペインから①「オートメーション」を選択し、②「Execute automation」を押下します。

続いて①「Owned by me」から CloudFormation で作成した②SSM ドキュメントを押下します。

続いて「オートメーションを実行する」を押下します。

ここの画面では実際に申請を行います。

Input parameters よりIamUserでアクセスキーを払い出したい IAM ユーザー名を、RequestReasonでは申請理由を記載します。
申請理由は日本語でも問題ありません。

入力が出来たら画面一番下の「Execute」を押下します。

申請者が申請を行うと、承認者へ下記のようにメールが送付されます。

承認者はメールを確認したら承認(Approve)または拒否(Reject)のリンクを選択するかコマンドベースで API 経由でも回答できます。

今回は承認してみます。

※承認者が承認または拒否するには承認者(approver)自身の IAM ユーザーでログインする必要があります。

Approve の右隣のリンクを選択すると次の画面が表示されます。

任意のコメントを入力して「Submit」を押下します。

すると後続の処理が開始し、aws:invokeLambdaFunctionが Success になれば完了です。

アクセスキー発行処理が完了すると承認者へメールが通知されます。

IAM ユーザーを見るとアクセスキーが発行されていることが確認でき、
Secret Manager を確認するとアクセスキーとシークレットアクセスキーが保管されていることが確認できます。

拒否またはアクセスキー取得失敗した場合

承認者が拒否した場合は、処理が失敗します。

続いてアクセスキーが既に 2 つ払い出されている場合には取得失敗し通知のみ実施されます。
こちらは Lambda の処理で判別しているため、SSM Automation 上では Success となります。

おわりに

今回はアクセスキー払い出しの承認ワークフローを作ってみました。

アクセスキーの使用は許容するが、セキュリティを考慮してあまり不用意に発行してほしくないと思う管理者もいらっしゃると思います。

Systems Manager Automation を活用することで比較的手軽に承認フローを作成できるため、ぜひ活用いただければと思います。

この記事がどなたかの役に立つと嬉しいです。