[CDK]AWS WorkSpacesへのログインを検知してSlack通知する

2021.10.27

吉川@広島です。

【新機能】WorkSpacesのログインイベントのトラッキングが可能になりました | DevelopersIO

案件でこちらの機能を使用する機会があったため、共有したいと思います。

大まかな構成は上記記事を踏襲していますが、差分は以下になります。

  • CDKで構築する
  • SNSからの通知先をメールではなくAWS Chatbot(Slack)とする
  • Lambdaコードの言語はPythonではなくNode.js(TypeScript)とする

AWS Chatbotの下準備

CloudWatch AlarmをAWS ChatbotからSlack通知する仕組みをCDKで作ってみた | DevelopersIO

こちらの「AWS Chatbot の Workspace Configuration を GUI から作成」の手順を実施します。

CDKコード

cdkコマンドでプロジェクトを生成します。

npx aws-cdk init --language typescript
// bin/cdk.ts

#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { CdkStack } from '../lib/cdk-stack'

const app = new cdk.App()
new CdkStack(app, 'workspaces-alarm-stack', {
  slackWorkspaceId: 'xxxxxxxxx', // SlackワークスペースID
  slackChannelId: 'xxxxxxxxx', // SlackチャンネルID
})
// lib/cdk-stack.ts

import * as cdk from '@aws-cdk/core'
import * as sns from '@aws-cdk/aws-sns'
import * as iam from '@aws-cdk/aws-iam'
import * as chatbot from '@aws-cdk/aws-chatbot'
import * as lambda from '@aws-cdk/aws-lambda'
import * as events from '@aws-cdk/aws-events'
import * as eventsTargets from '@aws-cdk/aws-events-targets'

export interface MyProps {
  readonly slackChannelId: string
  readonly slackWorkspaceId: string
}

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

    /**
     * SNS
     */
    const topic = new sns.Topic(this, 'topic')

    topic.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['SNS:Publish'],
        principals: [new iam.ServicePrincipal('events.amazonaws.com')],
        resources: [topic.topicArn],
      })
    )

    /**
     * Chatbot
     */
    const chatbotRole = new iam.Role(this, 'chatbot-role', {
      assumedBy: new iam.ServicePrincipal('sns.amazonaws.com'),
    })

    chatbotRole.addToPolicy(
      new iam.PolicyStatement({
        resources: ['*'],
        actions: ['cloudwatch:*'],
      })
    )

    new chatbot.CfnSlackChannelConfiguration(this, 'slack-channel-config', {
      configurationName: 'slack-channel-config',
      iamRoleArn: chatbotRole.roleArn,
      slackChannelId: props!.slackChannelId,
      slackWorkspaceId: props!.slackWorkspaceId,
      snsTopicArns: [topic.topicArn],
      loggingLevel: 'ERROR',
    })

    /**
     * Lambda
     */
    const fn = new lambda.Function(this, 'fn', {
      runtime: lambda.Runtime.NODEJS_14_X,
      code: lambda.Code.fromAsset('/path/to/dist'), // Bundle後のLambdaコードのPATHを指定
      handler: 'index.handler',
      environment: {
        SNS_TOPIC_ARN: topic.topicArn,
      },
    })
    topic.grantPublish(fn)

    /**
     * CloudWatch Events
     */
    new events.Rule(this, 'rule', {
      eventPattern: {
        source: ['aws.workspaces'],
        detailType: ['WorkSpaces Access'],
      },
      targets: [new eventsTargets.LambdaFunction(fn)],
    })
  }
}

Lambdaコード

CloudWatch Eventsをトリガーとして起動するLambdaコードを書きます。せっかくなので

  • TypeScriptで書いて
  • AWS SDK v3を使って
  • esbuildでバンドル

という感じでいきます。

npm i -S @aws-sdk/client-sns
npm i -D esbuild @types/node

SNSにPublishするだけのシンプルなLambda関数です。

import { PublishCommand, SNSClient } from '@aws-sdk/client-sns'

export const handler = (event: unknown) => {
  console.log(JSON.stringify(event))

  const snsClient = new SNSClient({})
  snsClient.send(
    new PublishCommand({
      TopicArn: process.env.SNS_TOPIC_ARN!,
      Message: JSON.stringify(event, null, 2),
      Subject: 'WorkSpaces Access Notification',
    })
  )
}
npx esbuild src/index.ts --platform=node --target=node14 --bundle --outfile=dist/index.js

動作確認

以上の設定でデプロイ後、AWS WorkSpacesのインスタンスにログインすると通知が来ることを確認できました。