1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Identity Center経由ログインのCloudTrailイベント調査と通知実装

1
Posted at

はじめに

Identity Center のアクセスポータルログイン後に、各AWSアカウントへログインするときのCloudTrailイベントを調査してみました。ログイン検知と通知も実装したところ、その過程でいろいろあったので、そのいろいろについても綴ります。

image.png

CloudTrailイベント調査

確認したパターン

  1. Organizations内アカウントへマネコンログイン
  2. Organizations内アカウントのアクセスキー取得
  3. Organizations外のAWSアカウントへマネコンログイン(AWS External Accountアプリケーション経由)

各パターンの画面操作イメージ

1. Organizations内アカウントへマネコンログイン

image.png

2. Organizations内アカウントのアクセスキー取得

image.png

3. Organizations外のAWSアカウントへマネコンログイン(AWS External Accountアプリケーション経由)

image.png

イベントの遷移

操作内容ごとに、下記のようなイベントを遷移することが確認できました。

操作 順序 eventName eventType eventSource
1. マネコンログイン 1 Federate AwsServiceEvent sso.amazonaws.com
2 AssumeRoleWithSAML AwsApiCall sts.amazonaws.com
3 GetSigninToken AwsConsoleSignIn signin.amazonaws.com
4 ConsoleLogin AwsConsoleSignIn signin.amazonaws.com
2. アクセスキー取得 1 GetRoleCredentials AwsServiceEvent sso.amazonaws.com
2 AssumeRoleWithSAML AwsApiCall sts.amazonaws.com
3. アプリケーションログイン 1 Federate AwsServiceEvent sso.amazonaws.com

各パターンのCloudTrailイベント詳細

大半の値はマスクしていますが、イベント情報の詳細を載せておきます。
Identity Centerが111111111111アカウントにあり、そこから222222222222アカウントに許可セットReadOnlyAccessSetでログインした、というシチュエーションです。

1. Organizations内アカウントへマネコンログイン

1.1. Federate

{
    "eventVersion": "1.10",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "1234abcd",
        "accountId": "111111111111",
        "userName": "xxx@xxx",
        "onBehalfOf": {
            "userId": "1234abcd",
            "identityStoreArn": "arn:aws:identitystore::111111111111:identitystore/d-1111111111"
        },
        "credentialId": "1111"
    },
    "eventTime": "2025-07-18T06:29:21Z",
    "eventSource": "sso.amazonaws.com",
    "eventName": "Federate",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aaa",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "111",
    "eventID": "111",
    "readOnly": false,
    "eventType": "AwsServiceEvent",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "serviceEventDetails": {
        "role_name": "ReadOnlyAccessSet",
        "account_id": "222222222222"
    },
    "eventCategory": "Management"
}

1.2. AssumeRoleWithSAML

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "SAMLUser",
        "principalId": "AAA:xxx@xxx",
        "userName": "xxx@xxx",
        "identityProvider": "AAA"
    },
    "eventTime": "2025-07-18T06:29:21Z",
    "eventSource": "sts.amazonaws.com",
    "eventName": "AssumeRoleWithSAML",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aws-internal/3 aws-sdk-java/1.12.782 Linux/4.14.355-277.647.amzn2.x86_64 OpenJDK_64-Bit_Server_VM/17.0.14+8-LTS java/17.0.14 vendor/Amazon.com_Inc. cfg/retry-mode/standard cfg/auth-source#imds m/N,P",
    "requestParameters": {
        "sAMLAssertionID": "111",
        "roleSessionName": "xxx@xxx",
        "roleArn": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd",
        "principalArn": "arn:aws:iam::222222222222:saml-provider/AWSSSO_1235abcd_DO_NOT_DELETE",
        "durationSeconds": 3600
    },
    "responseElements": {
        "credentials": {
            "accessKeyId": "AAA",
            "sessionToken": "BBB",
            "expiration": "Jul 18, 2025, 7:29:20 AM"
        },
        "assumedRoleUser": {
            "assumedRoleId": "AAA:xxx@xxx",
            "arn": "arn:aws:sts::222222222222:assumed-role/AWSReservedSSO_ReadOnlyAccessSet_1234abcd/xxx@xxx"
        },
        "subject": "xxx@xxx",
        "subjectType": "persistent",
        "issuer": "https://portal.sso.ap-northeast-1.amazonaws.com/saml/assertion/AAA",
        "audience": "https://signin.aws.amazon.com/saml",
        "nameQualifier": "AAA"
    },
    "additionalEventData": {
        "RequestDetails": {
            "endpointType": "regional",
            "awsServingRegion": "ap-northeast-1"
        }
    },
    "requestID": "111",
    "eventID": "111",
    "readOnly": true,
    "resources": [
        {
            "accountId": "222222222222",
            "type": "AWS::IAM::Role",
            "ARN": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd"
        },
        {
            "accountId": "222222222222",
            "type": "AWS::IAM::SAMLProvider",
            "ARN": "arn:aws:iam::222222222222:saml-provider/AWSSSO_1235abcd_DO_NOT_DELETE"
        }
    ],
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "222222222222",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "sts.ap-northeast-1.amazonaws.com"
    }
}

1.3. GetSigninToken

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AAA:xxx@xxx",
        "arn": "arn:aws:sts::222222222222:assumed-role/AWSReservedSSO_ReadOnlyAccessSet_1234abcd/xxx@xxx",
        "accountId": "222222222222",
        "accessKeyId": "AAA",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "BBB",
                "arn": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd",
                "accountId": "222222222222",
                "userName": "AWSReservedSSO_ReadOnlyAccessSet_1234abcd"
            },
            "attributes": {
                "creationDate": "2025-07-18T06:29:21Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2025-07-18T06:29:21Z",
    "eventSource": "signin.amazonaws.com",
    "eventName": "GetSigninToken",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "Jersey/${project.version} (HttpUrlConnection 17.0.14)",
    "requestParameters": null,
    "responseElements": {
        "credentials": {
            "accessKeyId": "AAA"
        },
        "GetSigninToken": "Success"
    },
    "additionalEventData": {
        "MobileVersion": "No",
        "MFAUsed": "No"
    },
    "eventID": "111",
    "readOnly": false,
    "eventType": "AwsConsoleSignIn",
    "managementEvent": true,
    "recipientAccountId": "222222222222",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "ap-northeast-1.signin.aws.amazon.com"
    }
}

1.4. ConsoleLogin

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AAA:xxx@xxx",
        "arn": "arn:aws:sts::222222222222:assumed-role/AWSReservedSSO_ReadOnlyAccessSet_1234abcd/xxx@xxx",
        "accountId": "222222222222",
        "accessKeyId": "AAA",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "BBB",
                "arn": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd",
                "accountId": "222222222222",
                "userName": "AWSReservedSSO_ReadOnlyAccessSet_1234abcd"
            },
            "attributes": {
                "creationDate": "2025-07-18T06:29:21Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2025-07-18T06:29:22Z",
    "eventSource": "signin.amazonaws.com",
    "eventName": "ConsoleLogin",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aaa",
    "requestParameters": null,
    "responseElements": {
        "ConsoleLogin": "Success"
    },
    "additionalEventData": {
        "MobileVersion": "No",
        "MFAUsed": "No"
    },
    "eventID": "111",
    "readOnly": false,
    "eventType": "AwsConsoleSignIn",
    "managementEvent": true,
    "recipientAccountId": "222222222222",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "ap-northeast-1.signin.aws.amazon.com"
    }
}

2. Organizations内アカウントのアクセスキー取得

2.1. GetRoleCredentials

{
    "eventVersion": "1.10",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "1234abcd",
        "accountId": "111111111111",
        "userName": "xxx@xxx",
        "onBehalfOf": {
            "userId": "1234abcd",
            "identityStoreArn": "arn:aws:identitystore::111111111111:identitystore/d-1111111111"
        },
        "credentialId": "1111"
    },
    "eventTime": "2025-07-18T06:29:37Z",
    "eventSource": "sso.amazonaws.com",
    "eventName": "GetRoleCredentials",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aaa",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "111",
    "eventID": "111",
    "readOnly": true,
    "eventType": "AwsServiceEvent",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "serviceEventDetails": {
        "role_name": "ReadOnlyAccessSet",
        "account_id": "222222222222"
    },
    "eventCategory": "Management"
}

2.2. AssumeRoleWithSAML

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "SAMLUser",
        "principalId": "AAA:xxx@xxx",
        "userName": "xxx@xxx",
        "identityProvider": "AAA"
    },
    "eventTime": "2025-07-18T06:29:37Z",
    "eventSource": "sts.amazonaws.com",
    "eventName": "AssumeRoleWithSAML",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aws-internal/3 aws-sdk-java/1.12.782 Linux/4.14.355-277.647.amzn2.x86_64 OpenJDK_64-Bit_Server_VM/17.0.14+8-LTS java/17.0.14 vendor/Amazon.com_Inc. cfg/retry-mode/standard cfg/auth-source#imds m/N,P",
    "requestParameters": {
        "sAMLAssertionID": "111",
        "roleSessionName": "xxx@xxx",
        "roleArn": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd",
        "principalArn": "arn:aws:iam::222222222222:saml-provider/AWSSSO_1234abcd_DO_NOT_DELETE",
        "durationSeconds": 3600
    },
    "responseElements": {
        "credentials": {
            "accessKeyId": "AAA",
            "sessionToken": "BBB",
            "expiration": "Jul 18, 2025, 7:29:36 AM"
        },
        "assumedRoleUser": {
            "assumedRoleId": "AAAA:xxx@xxx",
            "arn": "arn:aws:sts::222222222222:assumed-role/AWSReservedSSO_ReadOnlyAccessSet_1234abcd/xxx@xxx"
        },
        "subject": "xxx@xxx",
        "subjectType": "persistent",
        "issuer": "https://portal.sso.ap-northeast-1.amazonaws.com/saml/assertion/AAA",
        "audience": "https://signin.aws.amazon.com/saml",
        "nameQualifier": "AAA"
    },
    "additionalEventData": {
        "RequestDetails": {
            "endpointType": "regional",
            "awsServingRegion": "ap-northeast-1"
        }
    },
    "requestID": "111",
    "eventID": "111",
    "readOnly": true,
    "resources": [
        {
            "accountId": "222222222222",
            "type": "AWS::IAM::Role",
            "ARN": "arn:aws:iam::222222222222:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccessSet_1234abcd"
        },
        {
            "accountId": "222222222222",
            "type": "AWS::IAM::SAMLProvider",
            "ARN": "arn:aws:iam::222222222222:saml-provider/AWSSSO_1234abcd_DO_NOT_DELETE"
        }
    ],
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "222222222222",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "sts.ap-northeast-1.amazonaws.com"
    }
}

3. Organizations外のAWSアカウントへマネコンログイン(AWS External Accountアプリケーション経由)

3.1. Federate

{
    "eventVersion": "1.10",
    "userIdentity": {
        "type": "Unknown",
        "principalId": "1234abcd",
        "accountId": "111111111111",
        "userName": "xxx@xxx",
        "onBehalfOf": {
            "userId": "1234abcd",
            "identityStoreArn": "arn:aws:identitystore::111111111111:identitystore/d-1111111111"
        },
        "credentialId": "1111"
    },
    "eventTime": "2025-07-18T06:54:08Z",
    "eventSource": "sso.amazonaws.com",
    "eventName": "Federate",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aaa",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "111",
    "eventID": "111",
    "readOnly": false,
    "eventType": "AwsServiceEvent",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "serviceEventDetails": {
        "debug": "false",
        "app-id": "ins-12345678abcdefgh",
        "profile-id": "p-111"
    },
    "eventCategory": "Management"
}

※このとき利用したアプリケーションIDはapl-12345678abcdefghで、app-id12345678abcdefghの部分が一致していました。

"app-id": "ins-12345678abcdefgh",

通知実装

特定のアプリケーション(特定のAWSアカウントの特定のロール)にログインしたことを検知してSlackに通知させてみたいと思います。

image.png

具体的にハンドリングしたいイベントは 3.1. Federate で、下記を条件とすることで検知できそうです。

  • eventSourcesso.amazonaws.com
  • eventNameFederate
  • serviceEventDetails.app-idins-12345678abcdefgh ※ハンドリングしたいアプリIDを指定

User Notifications では設定できない?

最初に実装が楽な User Notificationsをマネコンから使ってみようと考えました。

イベントルールを設定する画面にて、Identityと入力したところ、AWS IdentityStore Serviceのみリストに表示されました。

image.png

User Notifications の通知設定を作成すると、EventBridge ルールが自動で作成されます。
AWSサービスにAWS IdentityStore Serviceを選択して作られる EventBridge ルールを確認したところ、イベントパターンは下記のようになっていました。

image.png

これは Events Reference の Identity Store events に該当します。

{
  "source": ["aws.identitystore"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["identitystore.amazonaws.com"]
  }
}

なお、Events Reference とは 2025/1/31 にリリースされたドキュメントで、AWSサービスが送信するイベントの仕様を確認することができます。

しかし、今回設定したイベントは、eventSource がsso.amazonaws.comであるため、Events Reference の AWS IAM Identity Center events に該当します。

{
  "source": ["aws.sso"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["sso.amazonaws.com"]
  }
}

そのため、マネジメントコンソールからは設定できない可能性があります。

次に、CDK で設定をしてみたところ、下記のように設定することができました。

image.png

CDKのコード(スタック作成部分の抜粋)は下記のとおりです。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class UserNotificationPocStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const cfnNotificationConfiguration =
      new cdk.aws_notifications.CfnNotificationConfiguration(
        this,
        'MyCfnNotificationConfiguration',
        {
          description: 'test',
          name: 'test',
        }
      );

    const eventPattern = {
      source: ['aws.sso'],
      detailType: ['AWS Service Event via CloudTrail'],
      detail: {
        eventSource: ['sso.amazonaws.com'],
        eventName: ['Federate'],
        serviceEventDetails: {
          'app-id': ['ins-12345678abcdefgh'],
        },
      },
    };

    const cfnEventRule = new cdk.aws_notifications.CfnEventRule(
      this,
      'MyCfnEventRule',
      {
        eventType: 'AWS Service Event via CloudTrail',
        notificationConfigurationArn: cfnNotificationConfiguration.attrArn,
        regions: ['ap-northeast-1'],
        source: 'aws.sso',
        eventPattern: JSON.stringify(eventPattern),
      }
    );

    const cfnEmailContact = new cdk.aws_notificationscontacts.CfnEmailContact(
      this,
      'MyCfnEmailContact',
      {
        emailAddress: 'hoge@fuga.com',
        name: 'hoge@fuga.com',
      }
    );

    const cfnChannelAssociation =
      new cdk.aws_notifications.CfnChannelAssociation(
        this,
        'MyCfnChannelAssociation',
        {
          arn: cfnEmailContact.attrArn,
          notificationConfigurationArn: cfnNotificationConfiguration.attrArn,
        }
      );
  }
}

ID がapl-12345678abcdefghのアプリケーション経由で Organizations 外のAWSアカウントにログインしてみたところ、CloudTrail に 3.1. Federate のイベントが出力されましたが、通知はされませんでした。このCDKによる実装の挙動についてはあまり追究できていないため、設定に誤りがあるだけの可能性もあります。

EventBridge では設定できた

ここまでの流れがあり、UserNotificationsでは設定できないのでは?と考え、EventBridge のルールを自前で作成してみることにしました。マネコンから作成を進めてみたところ、source にaws.ssoを指定することができました。これなら大丈夫そうな気がします。

image.png

最終的には下記のようにイベントパターンを書きました。

{
  "source": ["aws.sso"],
  "detail-type": ["AWS Service Event via CloudTrail"],
  "detail": {
    "eventSource": ["sso.amazonaws.com"],
    "eventName": ["Federate"],
    "serviceEventDetails": {
      "app-id": ["ins-12345678abcdefgh"]
    }
  }
}

ポイントは下記のとおりです。

  • detail-type をAWS Service Event via CloudTrailとする
  • アプリケーションIDはapp-idにリストで指定する
  • アプリケーションIDのプレフィックスはapl-ではなくins-とする

EventBridgeのターゲットにSNS Topicを設定し、「Amazon Q Developer in chat applications (旧称: AWS Chatbot)」(長いですね・・・)経由でSlackと繋げてみたところ、無事、イベントをハンドリングして通知することができました。

image.png

ですが、見てのとおり内容がありません・・・:sweat:

Slack メッセージに必要な情報を含めるために

そこで下記の2つを利用しました。

  • EventBridge の入力トランスフォーマー

  • Chatbot(長いのでこう呼ばせてください)のカスタム通知

EventBridge の入力トランスフォーマーでは、「入力パス」と「入力テンプレート」を設定する必要があります。

「入力パス」で、AWSサービスから受け取ったイベント情報(今回だとCloudTrail)から必要な情報を抽出します。

入力パス
{
    "detail-type": "$.detail-type",
    "account": "$.account",
    "eventTime": "$.detail.eventTime",
    "eventSource": "$.detail.eventSource",
    "eventName": "$.detail.eventName",
    "awsRegion": "$.detail.awsRegion",
    "userName": "$.detail.userIdentity.userName",
    "appId": "$.detail.serviceEventDetails.app-id"
}

「入力テンプレート」で、入力パスで抽出した値を使いつつ、イベントのターゲットに渡すメッセージフォーマットを整えることができます。
今回は SNS Topic 経由で Chatbotにメッセージを渡すため、Chatbot のカスタム通知の仕様 に合わせてメッセージを組み立てました。

入力テンプレート
{
  "version": "1.0",
  "source": "custom",
  "content": {
    "textType": "client-markdown",
    "title": ":information_resource: <detail-type> | <awsRegion> | Account:<account>",
    "description": "緊急用ロールの利用を検知しました。\n\n*EventTime:* <eventTime>\n*EventSource:* `<eventSource>`\n*EventName:* <eventName>\n*Region:* <awsRegion>\n*UserName:* `<userName>`\n*AppId:* <appId>"
  }
}

これらの設定をすることで、Slackに投稿されるメッセージに、必要な情報を含めることができました:tada:

image.png

できれば、アカウントIDではなくアカウント名が表示されたり、アプリケーションIDではなくアプリケーション名が表示できるといいのですが、その場合は、受け取ったイベントをいったん Lambda に渡して加工する、または Step Functions を使って加工する、などの対応が必要になりそうです。
(※このあと実装することになり、以降でその実装内容について触れていきます)

ユーザー名が表示されない

ここまでで実装完了と思っていたところ、翌日(7/25)届いた通知を見ると、昨日(7/24)は値が入っていたユーザー名が空になってしまっていました。

image.png

該当の時間帯の CloudTrail イベントを見てみたところ下記のようになっていました。

{
    "eventVersion": "1.10",
    "userIdentity": {
        "type": "IdentityCenterUser",
        "accountId": "111111111111",
        "onBehalfOf": {
            "userId": "1234abcd",
            "identityStoreArn": "arn:aws:identitystore::111111111111:identitystore/d-1111111111"
        },
        "credentialId": "1111"
    },
    "eventTime": "2025-07-24T03:54:00Z",
    "eventSource": "sso.amazonaws.com",
    "eventName": "Federate",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aaa",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "111",
    "eventID": "111",
    "readOnly": false,
    "eventType": "AwsServiceEvent",
    "managementEvent": true,
    "recipientAccountId": "111111111111",
    "serviceEventDetails": {
        "debug": "false",
        "app-id": "ins-12345678abcdefgh",
        "profile-id": "p-111"
    },
    "eventCategory": "Management"
}

userIdentityの部分を見てみると、下記の差分がありました。
左が最新(7/25)、右が前日(7/24)のものです。

image.png

調べてみると、6月頃にAWSサポートから下記の通知がきていました。(通知時点ではこの変更に影響を受ける実装はありませんでした)

この通知は、AWS IAM Identity Center のインスタンスをお持ちのお客様へお送りしております。以前に発表された IAM Identity Center による AWS CloudTrail フィールドの変更についてご案内いたします。これらの変更は 2025 年 7 月 14 日に有効になります。

これらの変更により、IAM Identity Center による CloudTrail へのユーザー固有の情報のロギングが最小限に抑えられると同時に、監査ワークフローにおけるユーザー識別が容易になります。これらの変更が 2025 年 7 月 14 日に有効になる前に、IAM Identity Center の CloudTrail イベントの userName、principalId、userIdentity type、group displayName フィールドを処理するワークフローを更新することをお勧めします。ユーザーの識別を簡単にするために、IAM Identity Center は CloudTrail の userIdentity 要素で userId と Identity Store Amazon Resource Name (ARN) を発行するようになりました。AWS セキュリティブログ記事「Important changes to CloudTrail events for AWS IAM Identity Center」[1] には、これらの変更に関する詳細と、ワークフローの更新方法に関するガイダンスが記載されています。

以下のリストは、主な活動と日程をまとめたものです。

  1. ワークフローをすぐに確認し、影響を受ける CloudTrail フィールドを使用している場合は更新することをお勧めします。 2025 年 7 月 14 日までにワークフローの更新を完了する必要があります。

  2. 2025 年 7 月 14 日から、既存のフィールド (userName、principalId、userIdentity type、group displayName) へ変更の実装を開始します。これらの変更は、2025 年 7 月 25 日までにすべての IAM Identity Center リージョンに導入される予定です。

このアップデートは2024年11月が初出で、下記のブログが書かれています。

ブログの中に下記の記述があり、今回の差分はまさにこれに該当しています。

IAM Identity Center now emits user ID and Identity Store Amazon Resource Name (ARN) fields to replace the userName and principalId fields, simplifying user identification. IAM Identity Center CloudTrail events will also specify IdentityCenterUser as the identity type instead of Unknown, providing a clear identifier for users.
(翻訳)
IAM Identity Centerはuser IDとIdentity Store Amazon Resource Name (ARN)フィールドを発行し、userNameとprincipalIdフィールドを置き換えることで、ユーザー識別を簡素化します。IAM Identity CenterのCloudTrailイベントでは、identity typeとしてUnknownではなくIdentityCenterUserを指定し、ユーザーを明確に識別します。

アプリケーションIDだけでなく、ユーザー名もIDになり、通知から誰がどこにログインしたかを判別することがさらに難しくなってしまいました・・・

ユーザー名とアプリケーション名を通知に表示する

ということで、EventBridgeで受け取ったイベントに対して、下記のカスタマイズをすることにしました。

  • ユーザーIDからユーザー名を取得
  • アプリケーションIDからアプリケーション名を取得
  • アカウントIDからアカウント名を取得
  • UTCを日本時間に変更

全体構成

image.png

Step Functions の中身

image.png

実装には CDK を利用しました。

また、Step Functions では JSONata を利用したので少し触れておきたいと思います。

Step Functions では JSONata と変数が 2024/11/22 に利用可能となり、以前の JSONPath と比べてシンプルにワークフローが定義できるようになりました。CDK においては、v2.178.0 (2025/2/6) から利用可能になりました。

個人的な感想としては、JSONPathだと値をステート間で継続して保持したり、ちょっとした処理に Lambda を用意しなければならなかったことが、見事にこれらのアップデートで改善されたので、とてもうれしいアップデートでした。ただし、JSONPath に最初は慣れが必要なのと同様に、JSONPath しか知らなかった状態から JSONata に切り替えるのもすんなりと理解できるものではなく、ある程度は慣れが必要だと感じました。

下記はコンストラクト部分を抜粋したものです。

コンストラクト抜粋
import {
  Duration,
  aws_events as events,
  aws_iam as iam,
  aws_logs as logs,
  RemovalPolicy,
  aws_stepfunctions as sfn,
  aws_sns as sns,
  aws_events_targets as targets,
  aws_stepfunctions_tasks as tasks,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export interface LoginNotificationProps {
  readonly systemName: string;
  readonly ssoInstanceId: string;
  readonly appList: string[];
  readonly topic: sns.ITopic;
}

export class LoginNotification extends Construct {
  constructor(scope: Construct, id: string, props: LoginNotificationProps) {
    super(scope, id);

    const parallel = sfn.Parallel.jsonata(this, 'Parallel');

    // UTCを日本時間に変更
    const convertToJst = sfn.Pass.jsonata(this, 'ConvertToJst', {
      outputs: {
        JstTime:
          '{% $fromMillis(($toMillis($states.input.detail.eventTime) + (9 * 60 * 60 * 1000)), "[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]+09:00") %}',
      },
    });

    // ユーザーIDからユーザー名を取得
    const describeUser = tasks.CallAwsService.jsonata(this, 'DescribeUser', {
      service: 'identitystore',
      action: 'describeUser',
      iamResources: ['*'],
      parameters: {
        IdentityStoreId:
          "{% $substringAfter($states.input.detail.userIdentity.onBehalfOf.identityStoreArn, 'identitystore/') %}",
        UserId: '{% $states.input.detail.userIdentity.onBehalfOf.userId %}',
      },
    });

    // アカウントIDからアカウント名を取得
    const describeAccount = tasks.CallAwsService.jsonata(
      this,
      'DescribeAccount',
      {
        service: 'organizations',
        action: 'describeAccount',
        iamResources: ['*'],
        parameters: {
          AccountId: '{% $states.input.account %}',
        },
      }
    );

    // アプリケーションIDからアプリケーション名を取得
    const describeApplication = tasks.CallAwsService.jsonata(
      this,
      'DescribeApplication',
      {
        service: 'ssoadmin',
        action: 'describeApplication',
        iamResources: ['*'],
        parameters: {
          ApplicationArn: `{% "arn:aws:sso::" & $states.input.account & ":application/${props.ssoInstanceId}/apl-" & $substringAfter($states.input.detail.serviceEventDetails.\'app-id\', \'ins-\') %}`,
        },
      }
    );

    // Chatbotカスタム通知のフォーマットでSNSパブリッシュ
    const snsPublish = tasks.SnsPublish.jsonata(this, 'SnsPublish', {
      topic: props.topic,
      message: sfn.TaskInput.fromObject({
        version: '1.0',
        source: 'custom',
        content: {
          title:
            '{% ":information_source: "  & $states.context.Execution.Input.\'detail-type\' & " | " & $states.context.Execution.Input.detail.awsRegion & " | " & $states.input[2].Account.Name & " | " & $states.context.Execution.Input.account %}',
          description:
            '{% "緊急用ロールの利用を検知しました。\n\n*EventTime:* " & $states.input[0].JstTime & "\n*EventSource:* `" & $states.context.Execution.Input.detail.eventSource & "`\n*EventName:* " & $states.context.Execution.Input.detail.eventName & "\n*UserEmail:* `" & $states.input[1].Emails[Primary=true].Value & "`\n*AppName:* " & $states.input[3].Name %}',
        },
      }),
    });

    const chain = sfn.Chain.start(
      parallel
        .branch(convertToJst)
        .branch(describeUser)
        .branch(describeAccount)
        .branch(describeApplication)
    ).next(snsPublish);

    const logGroup = new logs.LogGroup(this, 'LogGroup', {
      logGroupName: `/aws/vendedlogs/states/${props.systemName}`,
      retention: logs.RetentionDays.ONE_WEEK,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    const stateMachine = new sfn.StateMachine(this, 'StateMachine', {
      stateMachineName: `${props.systemName}-sfn`,
      definitionBody: sfn.DefinitionBody.fromChainable(chain),
      timeout: Duration.minutes(3),
      logs: {
        destination: logGroup,
        level: sfn.LogLevel.ERROR,
        includeExecutionData: true,
      },
    });

    // CDKによる自動のポリシー付与では正しく設定されないアクションを個別に許可
    stateMachine.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ['sso:DescribeApplication'],
        resources: ['*'],
      })
    );

    const target = new targets.SfnStateMachine(stateMachine, {
      retryAttempts: 3,
    });

    new events.Rule(this, 'Rule', {
      ruleName: `${props.systemName}-rule`,
      eventPattern: {
        source: ['aws.sso'],
        detailType: ['AWS Service Event via CloudTrail'],
        detail: {
          eventSource: ['sso.amazonaws.com'],
          eventName: ['Federate'],
          serviceEventDetails: {
            'app-id': props.appList,
          },
        },
      },
      targets: [target],
    });
  }
}

CDK のL2コンストラクトによるステートマシンの定義は、直観的に書いていけるところが良いと感じています。各ステートを個々に定義しておいて、それらのステートを繋げるときは下記のようにメソッドチェーンでシンプルに定義することができます。

    const chain = sfn.Chain.start(
      parallel
        .branch(convertToJst)
        .branch(describeUser)
        .branch(describeAccount)
        .branch(describeApplication)
    ).next(snsPublish);

1点、注意しなければならないポイントは下記の部分です。

    const describeApplication = tasks.CallAwsService.jsonata(
      this,
      'DescribeApplication',
      {
        service: 'ssoadmin',
        action: 'describeApplication',
        iamResources: ['*'],
        parameters: {
          ApplicationArn: `{% "arn:aws:sso::" & $states.input.account & ":application/${props.ssoInstanceId}/apl-" & $substringAfter($states.input.detail.serviceEventDetails.\'app-id\', \'ins-\') %}`,
        },
      }
    );

通常fromChainableメソッドでワークフローを定義すると、ステートマシンに必要なIAMポリシーをCDKが自動で付与してくれますが、上記DescribeApplicationアクションについては、サービスプレフィックスが正しく設定されません。

【誤】 ssoadmin:describeApplication ←自動でこちらが許可されるが誤り
【正】sso:DescribeApplication ←正しくはこちらの表記

そのため回避策として下記のように個別にポリシーステートメントを追加しました。

    // CDKによる自動のポリシー付与では正しく設定されないアクションを個別に許可
    stateMachine.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ['sso:DescribeApplication'],
        resources: ['*'],
      })
    );

マスクされていて変化が感じられませんが、この実装により、無事に通知の表記を改善することができました :tada:

image.png

さいごに

本記事では、Identity Center 経由でAWSログインした際のイベント情報の整理と、アプリケーション経由ログインの検知+通知実装の経験について記載しました。
本記事がどなたかの参考になればうれしいです。

弊社では一緒に働く仲間を募集中です!

現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!

募集内容等詳細は、是非採用サイトをご確認ください。

1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?