はじめに

こんにちは。アプリケーション開発部の谷野です。インキュベーションセンターが運営する自社サービス開発を担当しています。
そのサービスはインフラ環境をAWSで構成しており、システム運用のための仕掛けも同様にAWSで構成しています。
今回は運用業務の一環としてAWS Management Consoleのログインを記録する仕組みを構築した事例についてご紹介します。

本記事で利用している要件

  • AWS環境
    • AdministratorAccess権限を持つIAMユーザー
  • Terraform v15.4
  • 利用可能なメールアドレス

構成図

最終的な構成図は下図のようになります。

各AWSサービスの設定

Amazon CloudWatchのEvent Ruleの設定

まず、ログイン検知のきっかけとなるAmazon CloudWatchのEvent Ruleから構成しています。
このコードではAWS CloudTrailを介して AWS Console SingIn のアクションを検知する部分を構成しています。 なお、この検知機能は2021年6月現在東京リージョンでは提供されていないため、今回は北米リージョンを指定しています。

provider "aws" {
  alias  = "us"
  region = "us-east-1"
}

resource "aws_cloudwatch_event_rule" "console_login" {
  name        = "aws-console-login" //任意の名称
  description = ""

  event_pattern = <<PATTERN
{
  "detail-type": [
    "AWS Console Sign In via CloudTrail"
  ]
}
PATTERN
  provider      = aws.us
}

resource "aws_cloudwatch_event_target" "sns" {
  rule     = aws_cloudwatch_event_rule.console_login.name
  arn      = aws_sns_topic.login_alert.arn
  provider = aws.us
}

AWS SNS Topicの設定

検知されたイベントをAWS Lambda Functionに伝えるためのAWS SNS Topicを構成しています。
ここでは通知されるメール本文を加工する目的でLambdaを利用しているため、加工する必要がない場合はSNSからメールを送信することも可能です。

参考:Amazon SNS 通知の設定

resource "aws_sns_topic" "login_alert" {
  name     = "audit-alarm-to-mail-aws-console-login" //任意の名称
  provider = aws.us
}

resource "aws_sns_topic_policy" "default" {
  arn      = aws_sns_topic.login_alert.arn
  policy   = data.aws_iam_policy_document.sns_topic_policy.json
  provider = aws.us
}

resource "aws_sns_topic_subscription" "login_alert_mail" {
  topic_arn = aws_sns_topic.login_alert.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.login_alert_mail.arn
  provider  = aws.us
}

data "aws_iam_policy_document" "sns_topic_policy" {
  statement {
    effect  = "Allow"
    actions = ["sns:Publish"]

    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }

    resources = [aws_sns_topic.login_alert.arn]
  }
}

AWS Lambda Functionの設定

前項で記述したとおり、ここでは送信するメール本文を加工するためにLambda Functionを構成しています。 Node.jsのソースコードをsource_dirに配置してzip化したものをアップロードするとともに必要な環境変数を引き渡します。

なお、module.iam_assumable_role_login_alert_mail では以下の権限をLambdaに付与(assume-role)するためのポリシーを設定しています。

  • SESからメールを送信するses:Send*関連
  • ログ出力用のlogs:CreateLog*及びlogs:PutLogEvents
resource "aws_lambda_function" "login_alert_mail" {
  function_name    = "aws-console-login-alert-mail" //任意の名称
  role             = module.iam_assumable_role_login_alert_mail.iam_role_arn
  filename         = data.archive_file.this.output_path
  source_code_hash = data.archive_file.this.output_base64sha256
  runtime          = "nodejs14.x"
  handler          = "index.handler"

  memory_size = 128
  timeout     = 10

  environment {
    variables = {
      to_address   = 配信先メールアドレス
      from_address = 配信元メールアドレス
    }
  }
}

resource "aws_lambda_permission" "login_alert_mail" {
  action        = "lambda:invokeFunction"
  function_name = aws_lambda_function.login_alert_mail.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.login_alert.arn
}

data "archive_file" "this" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/dest/tmp.zip"
}

Amazon SESを起動するスクリプトです。

var aws = require('aws-sdk');
var ses = new aws.SES({region: 'ap-northeast-1'});

exports.handler = (event, context) => {

    const message = JSON.parse(event.Records[0].Sns.Message);

    notify_login(message);

    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

function notify_login(message) {
    const userType = message.detail.userIdentity.type;
    let loginUser;

    if (userType == 'Root') {
        loginUser = userType;
    } else if (userType == 'IAMUser') {
        let userName = message.detail.userIdentity.userName;
        loginUser = `${userType}:${userName}`;
    }

    send_mail("AWSコンソール-ログイン通知", `
    ${loginUser} がログインしました。
    時間    :${message.detail.eventTime}
    ソースIP:${message.detail.sourceIPAddress}
    `);
}

function send_mail(title, body) {

    var params = {
        Destination: {
            ToAddresses: [process.env.to_address]
        },
        Message: {
            Body: {
                Text: { Data: body }

            },

            Subject: { Data: title }
        },
        Source: process.env.from_address
    };


    ses.sendEmail(params, function (err, data) {
        if (err) {
            console.log(err);
            context.fail(err);
        }
    });
}

Amazon SESの設定

Amazon SESからメール通知する場合に必要な設定があります。

  • 送信元メールアドレスの認証(必須)

SESからメールを送信するためにはメールアドレス又はドメインの認証が必要です。 認証されていない状態ではメール通知が利用できないので下記の手順を参考にあらかじめ設定しておくようにしましょう。

参考:Amazon SES でのドメインの検証
参考:Amazon SES での E メールアドレスの検証

  • Amazon SESのサンドボックス解除(任意)

SESで送信元アドレス(ドメイン)を新たに登録すると、まずはサンドボックス環境として動作するようになります。 サンドボックス環境では送信数に制限がかかることと検証済みのメールアドレス宛にしか送信できない状態になっています。

不特定多数のメールアドレスに送信する場合はこの設定を解除する必要があります。 下記の手順を参考に必要に応じて解除しましょう。

参考:Amazon SES サンドボックス外への移動

実施結果

AWS Management Consoleにログインすると、下記のメールが飛んでくるようになります。

件名:AWSコンソールログイン通知

------------内容--------------
IAMUser:[IAMUser名] がログインしました。
時間    :2021-06-01T00:20:47Z
ソースIP:XXX.XXX.XXX.XXX
------------------------------

※ AWS、Amazon RDS CloudTrail、Amazon CloudWatch、Amazon SNS、AWS Lambda、Amazon SESは、米国および/またはその他の諸国における、Amazon.com, Inc.またはその関連会社の商標です。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。