私の戦闘力は53万です

awsとgcpについて書きます

AWS SAM がgithub actionsを使えるようになったので試してみた

f:id:remmemento:20210703211848p:plainf:id:remmemento:20210703211705p:plain

AWS SAMがgithub actionsを利用できるようになったようなので使ってみました。
使ってみるとめちゃくちゃ良かったので、記事にしてみました。

事前準備

まず何でも良いのでsamを作成します

sam init -r python3.8 -n github-actions-with-aws-sam --app-template "hello-world"

続いて.github/workflows/sam-pipeline.ymlを追加します
※regionとs3バケットを書き換えてください

on:
  push:
    branches:
      - main
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
      - uses: aws-actions/setup-sam@v1
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ##region##
      # sam build 
      - run: sam build --use-container

# Run Unit tests- Specify unit tests here 

# sam deploy
      - run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name sam-hello-world --s3-bucket ##s3-bucket## --capabilities CAPABILITY_IAM --region ##region##

credentialの登録

githubのsettings > secretsで下記を登録します。

pushしてみる

上記で準備ができましたのでmainにpushしてみます

git add .
git commit -m "add github actions"
git push origin main

f:id:remmemento:20210703204759p:plain

無事にactionsが完了しました。 APIGatewayが作成されていますので
実行してみるとレスポンスが返ってくることを確認できました。

curl -X GET  https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello
{"message": "hello world"}

変更してみる

さらに、lambdaを変更し、再度pushすると、
変更が反映されていることが確認できました。

cat hello_world/app.py

import json

# import requests


def lambda_handler(event, context):
  
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world update",
            # "location": ip.text.replace("\n", "")
        }),
    }
curl -X GET  https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello
{"message": "hello world update"}

テスト(pytest)を実行してみる

pytest利用する場合も試してみました。
簡単にテストを書いてみます

cat tests/unit/test_handler.py
import json
import pytest
from hello_world import app

def test_lambda_handler():

    ret = app.lambda_handler("", "")
    assert ret["statusCode"] == 200

def test_err_lambda_handler():
    ret = app.lambda_handler("", "")
    assert ret["statusCode"] == 400

github actionsでpytest可能なよう設定します

cat requirements.txt
pytest

.github/workflows/sam-pipeline.ymlにテスト部分を追記します

 # sam build 
      - run: sam build --use-container

      # Run Unit tests- Specify unit tests here 
      - run : pip install -r requirements.txt # 追記
      - run: pytest -s -k test_lambda_handler # 追記
      # - run: pytest -s -k test_err_lambda_handler # 追記

pytestが実行されることが確認できました f:id:remmemento:20210703205512p:plain

上記のコメントアウトしていた失敗する方のテストケースも流してみます。 .github/workflows/sam-pipeline.ymlを修正します

 # sam build 
      - run: sam build --use-container

      # Run Unit tests- Specify unit tests here 
      - run : pip install -r requirements.txt # 追記
      - run: pytest -s -k test_lambda_handler # 追記
      - run: pytest -s -k test_err_lambda_handler # 追記

きちんと失敗してくれていることが確認できました f:id:remmemento:20210703205549p:plain

失敗した場合は、後続の処理が行われず
デプロイがされずに止まってくれます。

最後に

個人的には今後samを利用するときは、
この使い方がベースになりそうなほど良かったです。

今回試してみたのの元ネタはこちらです。
細かい箇所はこちらも併せてご参考ください。 aws.amazon.com

AWS organizationsでcloudtrailを別Accountへ権限移譲する

AWS Organizationsの連携機能を色々と試しており、
cloudtrailでも連携機能を試してみました。

下記を参考にさせて頂いており、
KMSも含めて権限移譲をやろうとすると割と面倒で、
最終的にcloudformationを作成したのでブログ化してみました。 fu3ak1.hatenablog.com

Organizations とCloudtrail連携機能(AWS純正)

AWS Organizationsでは、Cloudtrailと連携し、
複数アカウントでのcloudtrail設定を簡単に実現できます。
cloudtrailで単純にorganizations連携をすると
下記のようになります。

f:id:remmemento:20210501182513p:plain
単純なorganizations+cloudtrail

上記でも十分にcloudtrailログを集約できるのですが
AWSのマルチアカウントのベストプラクティスでは、
ログ集約用の目的別のアカウント
(以下、LogArchiveアカウントと表現します)の別途作成を推奨しています。
要は何でもかんでも、親アカウントに集めるのは
権限が集約しすぎて良くないということですね。
上記理由から、純正機能でのcloudtrail集約をそのまま利用すると
ベストプラクティスに沿っていないのかと思いました。
aws.amazon.com

ベストプラクティス風にするには

LogArchiveアカウントを別にした構成を考えると
下記のように設定したいと考えました。

f:id:remmemento:20210501180719p:plain
構成イメージ

別途LogArchiveアカウントを作成し、
AdminAccountで収集したcloudtrailログを
LogArchiveアカウントに集約します。

上記、手動で設定しても良いですが、
割とポリシー系が面倒だったので、
cloudformationを作ってしてみました。

1.LogArchiveアカウントでの作業

f:id:remmemento:20210501180719p:plain まず、LogArchiveアカウントで、
S3とKMSを作成します。
LogArchiveアカウントで以下のcloudformationを適応します。

AWSTemplateFormatVersion: "2010-09-09"
Description: A templete for

Parameters:
  EnvPrefix:
    Type: String
    Default: OrgTrail
  orgAccountId:
    Type: String
    Default: xxxxxxxxxxxx
    Description: "Organization Admin Account Id"
  S3BucketName:
    Type: String
    Default: xxxxxxxxxxxxxxxxxxxxxxxxxx
    Description: "bucket name for cloudtrail"

Resources:
  mykms:
    Type: AWS::KMS::Key
    Properties:
      Description: !Sub ${EnvPrefix}Key
      Enabled: true
      EnableKeyRotation: true
      KeyUsage: ENCRYPT_DECRYPT
      PendingWindowInDays: 30
      KeyPolicy:
        Version: '2012-10-17'
        Id: key-policy
        Statement:
        - Sid: Enable IAM User Permissions
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
          Action: kms:*
          Resource: "*"
        - Sid: Allow use of the key
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${orgAccountId}:root
          Action:
          - kms:Encrypt
          - kms:Decrypt
          - kms:ReEncrypt*
          - kms:GenerateDataKey*
          - kms:DescribeKey
          Resource: "*"
        - Sid: Allow attachment of persistent resources
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${orgAccountId}:root
          Action:
          - kms:CreateGrant
          - kms:ListGrants
          - kms:RevokeGrant
          Resource: "*"
          Condition:
            Bool:
              kms:GrantIsForAWSResource: 'true'
        - Sid: Enable IAM User Permissions
          Effect: Allow
          Principal:
            AWS:
            - !Sub arn:aws:iam::${orgAccountId}:root
          Action: kms:*
          Resource: "*"
        - Sid: Allow CloudTrail to encrypt logs
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Action: kms:GenerateDataKey*
          Resource: "*"
          Condition:
            StringLike:
              'kms:EncryptionContext:aws:cloudtrail:arn': !Sub arn:aws:cloudtrail:*:${orgAccountId}:trail/*
        - Sid: Allow CloudTrail to describe key
          Effect: Allow
          Principal:
            Service: cloudtrail.amazonaws.com
          Action: kms:DescribeKey
          Resource: "*"
        - Sid: Allow principals in the account to decrypt log files
          Effect: Allow
          Principal:
            AWS: "*"
          Action:
          - kms:Decrypt
          - kms:ReEncryptFrom
          Resource: "*"
          Condition:
            StringEquals:
              kms:CallerAccount: !Ref orgAccountId
            StringLike:
              'kms:EncryptionContext:aws:cloudtrail:arn': !Sub arn:aws:cloudtrail:*:${orgAccountId}:trail/*
        - Sid: Allow alias creation during setup
          Effect: Allow
          Principal:
            AWS: "*"
          Action: kms:CreateAlias
          Resource: "*"
          Condition:
            StringEquals:
              kms:CallerAccount: !Ref orgAccountId
              kms:ViaService: ec2.us-east-1.amazonaws.com
        - Sid: Enable cross account log decryption
          Effect: Allow
          Principal:
            AWS: "*"
          Action:
          - kms:Decrypt
          - kms:ReEncryptFrom
          Resource: "*"
          Condition:
            StringEquals:
              kms:CallerAccount: !Ref orgAccountId
            StringLike:
              'kms:EncryptionContext:aws:cloudtrail:arn': !Sub arn:aws:cloudtrail:*:${orgAccountId}:trail/*

# Outputs:
#   Outputskmsid:
#     Description: kms-trail-organizations
#     Value: !GetAtt mykms.Arn
#     Export:
#       Name: !Sub ${EnvPrefix}-kms



  S3BucketOrg:
      Type: "AWS::S3::Bucket"
      Properties:
        BucketName: !Ref S3BucketName
        PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true

  S3BucketpublicdataBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref S3BucketOrg
        PolicyDocument:
          Statement:
            - Sid: AWSCloudTrailAclCheck
              Effect: Allow
              Principal:
                Service: cloudtrail.amazonaws.com
              Action: "s3:GetBucketAcl"
              Resource: !Sub arn:aws:s3:::${S3BucketName}
            - Sid: AWSCloudTrailWrite
              Effect: Allow
              Principal:
                Service: cloudtrail.amazonaws.com
              Action: 's3:PutObject'
              Resource: !Sub arn:aws:s3:::${S3BucketName}/AWSLogs/${orgAccountId}/*
              Condition:
                StringEquals:
                  's3:x-amz-acl': bucket-owner-full-control
            - Sid: AWSCloudTrailWriteMyaccount
              Effect: Allow
              Principal:
                Service: cloudtrail.amazonaws.com
              Action: 's3:PutObject'
              Resource: !Sub arn:aws:s3:::${S3BucketName}/AWSLogs/${AWS::AccountId}/*
              Condition:
                StringEquals:
                  's3:x-amz-acl': bucket-owner-full-control

2.AdminAccount(親アカウント)での作業

次にAdminAccount(organizationsの親アカウント)で、
cloudtrailを作成します。
この時、パラメータに上記で作成したLogArchiveアカウントの
S3の名前とKMSのidを設定します。
AdminAccountで以下のcloudformationを適応します。

AWSTemplateFormatVersion: "2010-09-09"
Description: A templete for taf init

Parameters:
  S3BucketName:
    Type: String
    Default: xxxxxxxxxxxxxxxxxx
    Description: "bucket name for cloudtrail. this is expected to be log Archive's account bucket name"
  kmsid:
    Type: String
    Default: 3e20a5ef-xxxxxxx-xxxx-xxxx
    Description: "kms-id in log Archive Account"
  AccountIdLog:
    Type: String
    Default: xxxxxxxxxx
    Description: "AWS Account Id of logArchive"

Resources :
  mytrail:
    Type: AWS::CloudTrail::Trail
    Properties:
#      CloudWatchLogsLogGroupArn: String
#      CloudWatchLogsRoleArn: String
      EnableLogFileValidation: true
#      EventSelectors:
#        - EventSelector
      IncludeGlobalServiceEvents: true
      IsLogging: true
      IsMultiRegionTrail: true
      KMSKeyId: !Sub arn:aws:kms:us-east-1:${AccountIdLog}:key/${kmsid}
      S3BucketName: !Ref S3BucketName
#      S3KeyPrefix: String
#      SnsTopicName: String
#      Tags:
#        - Resource Tag
      TrailName: !Ref S3BucketName

cloudtrailをorganizations連携する

上記適応後に、AWSコンソールから作成されたcloudtrailを開き、
「Enable for all accounts in my organization」を有効化します。
cloudformationでは、この設定値が設定できなかったので、
ここだけ手動設定になります。

f:id:remmemento:20210501190447p:plain
cloudtrail

上記でベストプラクティス風の構成が実現できました。 f:id:remmemento:20210501180719p:plain

さいごに

AWSでcloudtrailの権限移譲を構成してみました。
冒頭で紹介したブログは、cloudtrail以外にも、
Organiztions関連の連携まとめが記載されており分かりやすいです。
いつも参考にさせてもらっています。
ありがとうございます。
このブログもどなたか参考にして頂けると嬉しいです。 fu3ak1.hatenablog.com

AWS Control Towerのcloudformationを読み解いたので誰かに褒めてほしい

f:id:remmemento:20210527140256p:plain

AWS Control Towerを触ってみた

Contorl Towerの東京リージョンがサポートされたので触ってみました。
色々と調査する過程で、Control Towerを有効化した時に
裏側で流れるcloudformationを読み解いて図示したので
きっと誰かの役に立つと思いブログ化してみました。

Control Towerを有効化する前

簡単に、有効化前はorganizationsの配下にたくさんアカウントがいると仮定します。

Control Towerを有効化した後

control towerを有効化しても、既存アカウントに即座には影響はありません。

control towerが有効化されているOUの配下にアカウントを持ってくると
各種設定(cloudformationやらSCPやら)が有効化されます。

ここで、SCPは分かりやすくドキュメントに整理されていました。 docs.aws.amazon.com

ただ、cloudformationの方は、分かりやすく記載されたものが
見つけられませんでした。
ドキュメントをみてみると、cloudformaitonを確認するのが良いと書いてあったので、
大変でしたが自分で読み解いてみました。
読み解くと結構時間かかったので、
今後、同じ想いで苦労する人を救いたい一心で結果を図示してみました。

裏側のcloudformationを図示

全部読み解くと、下記のようになっていました。
control towerの全体図です。

裏側のcloudformation詳細

ここからは各stackの中身を記載していきます。
読み解く時にはメモが参考になると思います。
もし間違いを見つけた場合は、ご連絡いただけますと嬉しいです。

AWSControlTowerBP-BASELINE-CLOUDTRAIL

概要

trailを有効化し結果をS3とlogsに出力する
S3はアーカイブアカウント宛で、マルチリージョンを記録します
logs連携は有効化され、retentionは14日
configはsnsを設定、セキュリティアカウントの「aws-controltower-AllConfigNotification」に通知 します

デプロイ対象

下記のcontroltowerを有効化した1つのリージョン
core ou
custome ou

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure AWS CloudTrail

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'

  EnableLogFileValidation:
    Type: String
    Default: 'true'
    Description: Indicates whether CloudTrail validates the integrity of log files.
    AllowedValues:
      - 'true'
      - 'false'

  IncludeGlobalEvents:
    Type: String
    Default: 'true'
    Description: Indicates whether the trail is publishing events from global services, such as IAM, to the log files.
    AllowedValues:
      - 'true'
      - 'false'

  MultiRegion:
    Type: String
    Default: 'true'
    Description: Indicates whether the CloudTrail trail is created in the region in which you create the stack (false) or in all regions (true).
    AllowedValues:
      - 'true'
      - 'false'

  AllConfigTopicName:
    Type: String
    Default: ''
    Description: All Configuration Notification SNS Topic in Security Account that AWS Config delivers notifications to.

  SecurityAccountId:
    Type: 'String'
    Description: AWS Account Id of the Security account.

  AuditBucketName:
    Type: String
    Default: ''
    Description: Audit Bucket name from the Log Archive Account

  PublishToCloudWatchLogs:
    Type: String
    Default: 'true'
    Description: Indicates whether notifications are published to CloudWatch Logs.
    AllowedValues:
      - 'true'
      - 'false'

  LogsRetentionInDays:
    Description: 'Specifies the number of days you want to retain CloudTrail log events in the CloudWatch Logs.'
    Type: Number
    Default: 14
    AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]

  AWSLogsS3KeyPrefix:
    Type: 'String'
    Description: 'Organization ID to use as the S3 Key prefix for storing the audit logs'

Conditions:
  IsMultiRegion: !Equals
    - !Ref MultiRegion
    - 'true'

  IsPublishToCloudWatchLogs: !Equals
    - !Ref PublishToCloudWatchLogs
    - 'true'

Resources:
  Trail:
    Type: AWS::CloudTrail::Trail
    Properties:
      TrailName: !Sub ${ManagedResourcePrefix}-BaselineCloudTrail
      S3BucketName: !Ref AuditBucketName
      S3KeyPrefix: !Ref AWSLogsS3KeyPrefix
      SnsTopicName: !Sub arn:aws:sns:${AWS::Region}:${SecurityAccountId}:${AllConfigTopicName}
      IsLogging: True
      EnableLogFileValidation: !Ref EnableLogFileValidation
      IncludeGlobalServiceEvents: !If
        - IsMultiRegion
        - True
        - !Ref IncludeGlobalEvents
      IsMultiRegionTrail: !Ref MultiRegion
      CloudWatchLogsLogGroupArn: !If
        - IsPublishToCloudWatchLogs
        - !GetAtt TrailLogGroup.Arn
        - !Ref AWS::NoValue
      CloudWatchLogsRoleArn: !If
        - IsPublishToCloudWatchLogs
        - !GetAtt TrailLogGroupRole.Arn
        - !Ref AWS::NoValue

  TrailLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Condition: IsPublishToCloudWatchLogs
    Properties:
      LogGroupName: !Sub ${ManagedResourcePrefix}/CloudTrailLogs
      RetentionInDays: !Ref LogsRetentionInDays

  TrailLogGroupRole:
     Type: 'AWS::IAM::Role'
     Condition: IsPublishToCloudWatchLogs
     Properties:
       RoleName: !Sub ${ManagedResourcePrefix}-CloudWatchLogsRole
       AssumeRolePolicyDocument:
         Version: '2012-10-17'
         Statement:
         - Sid: CloudTrailAssumeRole
           Effect: Allow
           Principal:
             Service: 'cloudtrail.amazonaws.com'
           Action: 'sts:AssumeRole'
       Policies:
       - PolicyName: 'cloudtrail-policy'
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
           - Sid: AWSCloudTrailCreateLogStream
             Effect: Allow
             Action: 'logs:CreateLogStream'
             Resource: !GetAtt 'TrailLogGroup.Arn'
           - Sid: AWSCloudTrailPutLogEvents
             Effect: Allow
             Action: 'logs:PutLogEvents'
             Resource: !GetAtt 'TrailLogGroup.Arn'

Outputs:
  BaselineCloudTrail:
    Description: Baseline CloudTrail
    Value: !GetAtt 'Trail.Arn'
  CloudWatchLogsGroupArn:
    Description: CloudWatch Log Group ARN for Baseline CloudTrail
    Value: !GetAtt 'TrailLogGroup.Arn'
  CloudWatchLogsGroupName:
    Description: CloudWatch Log Group Name for Baseline CloudTrail
    Value: !Ref TrailLogGroup

AWSControlTowerBP-BASELINE-CLOUDWATCH

概要

LambdaとSNS
configのルールに準拠していない場合は、子アカウントのLambdaを経由して
AuditアカウントのSNSに通知する
デフォルトではSNSの名前は下記になっている
aws-controltower-AggregateSecurityNotifications

デプロイ対象

下記のcontroltowerがサポートされている全リージョン
core ou
custome ou

cloudformation template
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Configure Cloudwatch Rule, local SNS Topic, forwarding notifications from local SNS Topic to Security Topic
Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'
  SecurityTopicName:
    Type: String
    Description: Security Notification SNS Topic Name.
  SecurityAccountId:
    Type: 'String'
    Description: AWS Account Id of the Security account.
  LogsRetentionInDays:
    Description: 'Specifies the number of days you want to retain notification forwarding log events in the Lambda log group.'
    Type: Number
    Default: 14
    AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653]
  EnableConfigRuleComplianceChangeAlarm:
    Type: String
    Description: "Enable notifications for AWS Config rule compliance status changes?"
    Default: true
    AllowedValues:
    - true
    - false

Mappings:
  TopicNameSuffix:
    LocalTopicName:
      Suffix: 'SecurityNotifications'

Conditions:
  EnableConfigRuleChangeNotification: !Equals
    - !Ref EnableConfigRuleComplianceChangeAlarm
    - 'true'

Resources:

  ForwardSnsNotification:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: !Sub ${ManagedResourcePrefix}-NotificationForwarder
      Description: SNS message forwarding function for aggregating account notifications.
      Code:
        ZipFile:
          !Sub |
            from __future__ import print_function
            import boto3
            import json
            import os
            def lambda_handler(event, context):
                #print("Received event: " + json.dumps(event, indent=2))
                sns = boto3.client('sns')
                subject=event['Records'][0]['Sns']['Subject']
                if subject is None:
                    subject = 'None'
                message = event['Records'][0]['Sns']['Message']
                try:
                    msg = json.loads(message)
                    message = json.dumps(msg, indent=4)
                    if 'detail-type' in msg:
                      subject = msg['detail-type']
                except:
                    print('Not json')
                response = sns.publish(
                    TopicArn=os.environ.get('sns_arn'),
                    Subject=subject,
                    Message=message
                )
                print(response)
                return response
      Handler: 'index.lambda_handler'
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${ManagedResourcePrefix}-ForwardSnsNotificationRole
      Runtime: 'python3.6'
      Timeout: 60
      Environment:
        Variables:
          sns_arn: !Sub arn:aws:sns:${AWS::Region}:${SecurityAccountId}:${SecurityTopicName}

  ForwardSnsNotificationGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/aws/lambda/${ForwardSnsNotification}'
      RetentionInDays: !Ref LogsRetentionInDays

  LocalSecurityTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, LocalTopicName, Suffix] ]]
      TopicName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, LocalTopicName, Suffix] ]]

  SNSNotificationPolicy:
    Type: AWS::SNS::TopicPolicy
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: F18
            reason: "Condition restricts permissions to current account."
    Properties:
      Topics:
        - !Ref LocalSecurityTopic
      PolicyDocument:
        Statement:
          - Sid: __default_statement_ID
            Effect: Allow
            Principal:
              AWS: "*"
            Action:
            - SNS:GetTopicAttributes
            - SNS:SetTopicAttributes
            - SNS:AddPermission
            - SNS:RemovePermission
            - SNS:DeleteTopic
            - SNS:Subscribe
            - SNS:ListSubscriptionsByTopic
            - SNS:Publish
            - SNS:Receive
            Resource: !Ref LocalSecurityTopic
            Condition:
              StringEquals:
                AWS:SourceOwner: !Sub ${AWS::AccountId}
          - Sid: TrustCWEToPublishEventsToMyTopic
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sns:Publish
            Resource: !Ref LocalSecurityTopic

  SNSNotificationSubscription:
    Type: "AWS::SNS::Subscription"
    Properties:
      Endpoint: !GetAtt ForwardSnsNotification.Arn
      Protocol: lambda
      TopicArn: !Ref LocalSecurityTopic

  SNSInvokeLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      Principal: sns.amazonaws.com
      SourceArn: !Ref LocalSecurityTopic
      FunctionName: !GetAtt ForwardSnsNotification.Arn

  # Enable notifications for AWS Config Rule compliance changes
  CWEventRuleComplianceChangeEvent:
    Type: AWS::Events::Rule
    Condition: EnableConfigRuleChangeNotification
    Properties:
      Name: !Sub ${ManagedResourcePrefix}-ConfigComplianceChangeEventRule
      Description: 'CloudWatch Event Rule to send notification on Config Rule compliance changes.'
      EventPattern:
        {
          "source": [
            "aws.config"
          ],
          "detail-type": [
            "Config Rules Compliance Change"
          ]
        }
      State: ENABLED
      Targets:
      - Id: !Sub 'Compliance-Change-Topic'
        Arn: !Ref LocalSecurityTopic

Outputs:
  LocalSecurityTopic:
    Description: Local Security Notification SNS Topic ARN
    Value: !Ref LocalSecurityTopic
  LocalSecurityTopicName:
    Description: Local Security Notification SNS Topic Name
    Value: !GetAtt LocalSecurityTopic.TopicName

AWSControlTowerBP-BASELINE-CONFIG

概要

configを有効化し、security(Audit)のアカウントにアグリゲーションする configの通知はsecurity(Audit)のアカウントの下記に通知する aws-controltower-AllConfigNotifications

デプロイ対象

下記のcontroltowerがサポートされている全リージョン
core ou
custome ou

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure AWS Config

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'

  AllSupported:
    Type: String
    Default: 'true'
    Description: Indicates whether to record all supported resource types.
    AllowedValues:
      - 'true'
      - 'false'

  IncludeGlobalResourceTypes:
    Type: String
    Default: 'true'
    Description: Indicates whether AWS Config records all supported global resource types.
    AllowedValues:
      - 'true'
      - 'false'

  ResourceTypes:
    Type: CommaDelimitedList
    Description: A list of valid AWS resource types to include in this recording group. Eg. AWS::CloudTrail::Trail

  Frequency:
    Type: String
    Default: 1hour
    Description: The frequency with which AWS Config delivers configuration snapshots.
    AllowedValues:
      - 1hour
      - 3hours
      - 6hours
      - 12hours
      - 24hours

  AllConfigTopicName:
    Type: String
    Default: ''
    Description: All Configuration Notification SNS Topic in Security Account that AWS Config delivers notifications to.

  SecurityAccountId:
    Type: 'String'
    Description: AWS Account Id of the Security account.

  AuditBucketName:
    Type: String
    Default: ''
    Description: Audit Bucket name from the Log Archive Account

  AWSLogsS3KeyPrefix:
    Type: 'String'
    Description: Organization ID to use as the S3 Key prefix for storing the audit logs

  HomeRegionName:
    Type: 'String'
    Description: The name of the home region for the customer

  IsHomeRegionInitialControlTowerRegion:
    Type: 'String'
    AllowedValues:
       - 'true'
       - 'false'
    Description: Indicates whether the Home Region of the customer is one of the initial regions in which AWS Control Tower launched.

Conditions:
  IsAllSupported: !Equals
    - !Ref AllSupported
    - 'true'

  CreateHomeRegionConfigAggregatorAuthorization: !Equals
   -  !Ref IsHomeRegionInitialControlTowerRegion
   -  'false'

  CreateRegionalConfigAggregatorAuthorization: !Equals
   -  !Ref IsHomeRegionInitialControlTowerRegion
   -  'true'

Mappings:
  Settings:
    FrequencyMap:
      1hour   : One_Hour
      3hours  : Three_Hours
      6hours  : Six_Hours
      12hours : Twelve_Hours
      24hours : TwentyFour_Hours

Resources:
  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    Properties:
      Name: !Sub ${ManagedResourcePrefix}-BaselineConfigRecorder
      RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/${ManagedResourcePrefix}-ConfigRecorderRole
      RecordingGroup:
        AllSupported: !Ref AllSupported
        IncludeGlobalResourceTypes: !Ref IncludeGlobalResourceTypes
        ResourceTypes: !If
          - IsAllSupported
          - !Ref AWS::NoValue
          - !Ref ResourceTypes

  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    Properties:
      Name: !Sub ${ManagedResourcePrefix}-BaselineConfigDeliveryChannel
      ConfigSnapshotDeliveryProperties:
        DeliveryFrequency: !FindInMap
          - Settings
          - FrequencyMap
          - !Ref Frequency
      S3BucketName: !Ref AuditBucketName
      S3KeyPrefix: !Ref AWSLogsS3KeyPrefix
      SnsTopicARN: !Sub arn:aws:sns:${AWS::Region}:${SecurityAccountId}:${AllConfigTopicName}

  AuthorizerPdx:
    Condition: CreateRegionalConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: us-west-2
  AuthorizerIad:
    Condition: CreateRegionalConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: us-east-1
  AuthorizerCmh:
    Condition: CreateRegionalConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: us-east-2
  AuthorizerDub:
    Condition: CreateRegionalConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: eu-west-1
  AuthorizerSyd:
    Condition: CreateRegionalConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: ap-southeast-2
  HomeRegionAuthorizer:
    Condition: CreateHomeRegionConfigAggregatorAuthorization
    Type: "AWS::Config::AggregationAuthorization"
    Properties:
      AuthorizedAccountId: !Ref SecurityAccountId
      AuthorizedAwsRegion: !Ref HomeRegionName

Outputs:
  BaselineConfigRecorder:
    Description: Baseline Config Recorder
    Value:  !Ref ConfigRecorder
  BaselineConfigDeliveryChannel:
    Description: Baseline Config Delivery Channel
    Value: !Ref ConfigDeliveryChannel

AWSControlTowerBP-BASELINE-ROLES

概要

Auditのアカウントからスイッチロールが可能なよう
readonlyとadminの2種類のroleを作成する

デプロイ対象

下記のcontroltowerが有効化された単一リージョン
core ou
custome ou

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the Cross-Account IAM Security Roles for the member accounts.

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'
  SecurityAccountAdminRoleArn:
    Type: String
    Description: Admin role ARN from the security account.
  SecurityAccountReadOnlyRoleArn:
    Type: String
    Description: Admin role ARN from the security account.
  EnableAdminRole:
    Type: String
    Default: 'true'
    Description: Create an administrative cross-account role from Security Account to this account.
    AllowedValues:
      - 'true'
      - 'false'
  EnableReadOnlyRole:
    Type: String
    Default: 'true'
    Description: Create a read-only cross-account role from Security Account to this account.
    AllowedValues:
      - 'true'
      - 'false'

Conditions:
  CreateAdminRole: !Equals
    - !Ref EnableAdminRole
    - 'true'
  CreateReadOnlyRole: !Equals
    - !Ref EnableReadOnlyRole
    - 'true'

Resources:
  AdminExecutionRole:
    Type: AWS::IAM::Role
    Condition: CreateAdminRole
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-AdministratorExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref SecurityAccountAdminRoleArn
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

  ReadOnlyExecutionRole:
    Type: AWS::IAM::Role
    Condition: CreateReadOnlyRole
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-ReadOnlyExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref SecurityAccountReadOnlyRoleArn
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess

AWSControlTowerBP-BASELINE-SERVICE-ROLES

概要

AWSControlTowerBP-BASELINE-CLOUDWATCH
の実行に必要なlambdaとconfig用のロールを作成する

デプロイ対象

下記のcontroltowerが有効化された単一リージョン core ou custome ou

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure AWS Config and SNS Notification Forward IAM Roles

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'

  SecurityTopicName:
    Type: String
    Description: Security Notification SNS Topic Name.

  SecurityAccountId:
    Type: 'String'
    Description: AWS Account Id of the Security account.

Resources:

  ConfigRecorderRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-ConfigRecorderRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSConfigRole
        - arn:aws:iam::aws:policy/ReadOnlyAccess

  ForwardSnsNotificationLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-ForwardSnsNotificationRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: 'lambda.amazonaws.com'
          Action:
          - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
      - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
      - PolicyName: sns
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - 'sns:publish'
            Resource: !Sub arn:aws:sns:*:${SecurityAccountId}:${SecurityTopicName}

Outputs:
  BaselineConfigRole:
    Description: Baseline Config Role
    Value: !GetAtt 'ConfigRecorderRole.Arn'

AWSControlTowerBP-SECURITY-TOPICS

概要

SNSのtopicを2つ作成する
AggregateSecurityNotifications
AllConfigNotifications
各アカウントで何かあったときの受け口となるSNS Topic

対象

下記のcontroltowerがサポートされている全リージョン audit

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the SNS Topics for Security Account

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'
  AllConfigurationEmail:
    Type: 'String'
    Description: Email for receiving all AWS configuration events
  SecurityNotificationEmail:
    Type: 'String'
    Description: Email for the security administrator(s)
  OrgID:
    Type: 'String'
    Description: AWS Organizations ID to allow notifications from member accounts
  SubscribeToAllConfigurationTopic:
    Type: String
    Default: false
    Description: Indicates whether AllConfigurationEmail will be subscribed to the AllConfigurationTopicName topic.
    AllowedValues:
      - true
      - false

Conditions:
  Subscribe: !Equals
    - !Ref SubscribeToAllConfigurationTopic
    - 'true'

Mappings:
  TopicNameSuffix:
    AllConfigurationTopicName:
      Suffix: 'AllConfigNotifications'
    NotifyTopicName:
      Suffix: 'AggregateSecurityNotifications'

Resources:
  SNSAllConfigurationTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, AllConfigurationTopicName, Suffix] ]]
      TopicName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, AllConfigurationTopicName, Suffix] ]]

  SNSAllConfigurationTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      Topics:
        - !Ref SNSAllConfigurationTopic
      PolicyDocument:
        Statement:
          - Sid: AWSSNSPolicy
            Action:
              - sns:Publish
            Effect: Allow
            Resource: !Ref SNSAllConfigurationTopic
            Principal:
              Service:
                - cloudtrail.amazonaws.com
                - config.amazonaws.com

  SNSAllConfigurationEmailNotification:
    Condition: Subscribe
    Type: AWS::SNS::Subscription
    Properties:
      Endpoint: !Ref AllConfigurationEmail
      Protocol: email
      TopicArn: !Ref SNSAllConfigurationTopic

  SNSNotification:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, NotifyTopicName, Suffix] ]]
      TopicName: !Join [ "-", [ !Ref ManagedResourcePrefix, !FindInMap [TopicNameSuffix, NotifyTopicName, Suffix] ]]
      Subscription:
      - Protocol: email
        Endpoint: !Ref SecurityNotificationEmail

  SNSNotificationPolicy:
    Type: AWS::SNS::TopicPolicy
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: F18
            reason: "Conditions restrict permissions to Organization account and publishing only to member accounts."
    Properties:
      Topics:
        - !Ref SNSNotification
      PolicyDocument:
        Statement:
          - Sid: __default_statement_ID
            Effect: Allow
            Principal:
              AWS: "*"
            Action:
            - SNS:GetTopicAttributes
            - SNS:SetTopicAttributes
            - SNS:AddPermission
            - SNS:RemovePermission
            - SNS:DeleteTopic
            - SNS:Subscribe
            - SNS:ListSubscriptionsByTopic
            - SNS:Publish
            - SNS:Receive
            Resource: !Ref SNSNotification
            Condition:
              StringEquals:
                AWS:SourceOwner: !Sub ${AWS::AccountId}
          - Sid: AWSSNSPolicy
            Effect: Allow
            Principal:
              AWS: "*"
            Action: sns:Publish
            Resource: !Ref SNSNotification
            Condition:
              StringEquals:
                aws:PrincipalOrgID: !Ref OrgID

Outputs:
  SecurityTopicARN:
    Description: Security Notification SNS Topic ARN
    Value: !Ref SNSNotification
  SecurityTopicName:
    Description: Security Notification SNS Topic Name
    Value: !GetAtt SNSNotification.TopicName
  AllConfigTopicARN:
    Description: All Configuration Notification SNS Topic ARN
    Value: !Ref SNSAllConfigurationTopic
  AllConfigTopicName:
    Description: All Configuration Notification SNS Topic Name
    Value: !GetAtt SNSAllConfigurationTopic.TopicName

AWSControlTowerGuardrailAWS-GR-AUDIT-BUCKET-PUBLIC-READ-PROHIBITED

概要

config ruleを作成する
ルール:S3_BUCKET_PUBLIC_READ_PROHIBITED

デプロイ対象

下記のcontroltowerがサポートされている全リージョン Log archive
audit

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure AWS Config rules to check that your S3 buckets do not allow public access

Parameters:
  ConfigRuleName:
    Type: 'String'
    Description: 'Name for the Config rule'

Resources:
  CheckForS3PublicRead:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: !Sub ${ConfigRuleName}
      Description: Checks that your S3 buckets do not allow public read access. If an S3 bucket policy or bucket ACL allows public read access, the bucket is noncompliant.
      Source:
        Owner: AWS
        SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
      Scope:
        ComplianceResourceTypes:
          - AWS::S3::Bucket

AWSControlTowerGuardrailAWS-GR-AUDIT-BUCKET-PUBLIC-WRITE-PROHIBITED

概要

config ruleを作成する
ルール:S3_BUCKET_PUBLIC_WRITE_PROHIBITED

デプロイ対象

下記のcontroltowerがサポートされている全リージョン
Log archive
audit

AWSTemplateFormatVersion: 2010-09-09
Description: Configure AWS Config rules to check that your S3 buckets do not allow public access

Parameters:
  ConfigRuleName:
    Type: 'String'
    Description: 'Name for the Config rule'

Resources:
  CheckForS3PublicWrite:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: !Sub ${ConfigRuleName}
      Description: Checks that your S3 buckets do not allow public write access. If an S3 bucket policy or bucket ACL allows public write access, the bucket is noncompliant.
      Source:
        Owner: AWS
        SourceIdentifier: S3_BUCKET_PUBLIC_WRITE_PROHIBITED
      Scope:
        ComplianceResourceTypes:
          - AWS::S3::Bucket

AWSControlTowerLoggingResources

概要

バケットを2つ作成する
1つはaudit。ここに他アカウントからのconfigやtrail結果が書き込まれる
2つ目は上記のs3へのアクセスログを記録するためのバケット

デプロイ対象

下記のcontroltowerが有効化された単一リージョン
Log archive

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure an Audit S3 bucket for the Log Archive account.

Parameters:
  SSEAlgorithm:
    Type: 'String'
    Default: 'AES256'
    Description: S3 bucket SSE Algorithm.
    AllowedValues:
    - 'AES256'
    - 'aws:kms'
  KMSMasterKeyID:
    Type: 'String'
    Description: 'KMS key ID required if SSE algorithm is aws:kms.'
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'
  RetentionDays:
    Type: String
    Description: 'No of Days to retain the logs, after which it will be permanently deleted'
  TransitionToGlacier:
    Type: String
    Description: 'Do you wish to transition the logs to Glacier before permanently deleting?'
    Default: 'No'
    AllowedValues:
    - 'Yes'
    - 'No'
  TransitionDays:
    Type: String
    Description: 'No of Days to transition the data from S3 to Glacier'
  AWSLogsS3KeyPrefix:
    Type: 'String'
    Description: 'Organization ID to use as the S3 Key prefix for storing the audit logs'

Conditions:
  UseKMS: !Equals
    - !Ref SSEAlgorithm
    - 'aws:kms'
  MoveToGlacier: !Equals
    - !Ref TransitionToGlacier
    - 'Yes'

Resources:
  # Create S3 Server Access Logging bucket
  S3LoggingBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${ManagedResourcePrefix}-s3-access-logs-${AWS::AccountId}-${AWS::Region}
      AccessControl: LogDeliveryWrite
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - !If
          - UseKMS
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: !Ref SSEAlgorithm
              KMSMasterKeyID: !Ref KMSMasterKeyID
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: !Ref SSEAlgorithm
  # Create S3 Audit bucket
  S3AuditBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${ManagedResourcePrefix}-logs-${AWS::AccountId}-${AWS::Region}
      VersioningConfiguration:
        Status: Enabled
      LoggingConfiguration:
        DestinationBucketName: !Ref S3LoggingBucket
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - !If
          - UseKMS
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: !Ref SSEAlgorithm
              KMSMasterKeyID: !Ref KMSMasterKeyID
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: !Ref SSEAlgorithm
      LifecycleConfiguration:
        Rules:
        - !If
          - MoveToGlacier
          - Id: RetentionRule
            Status: Enabled
            ExpirationInDays: !Ref RetentionDays
            NoncurrentVersionExpirationInDays: !Ref RetentionDays
            Transitions:
                - TransitionInDays: !Ref TransitionDays
                  StorageClass: Glacier
            NoncurrentVersionTransitions:
                - TransitionInDays: !Ref TransitionDays
                  StorageClass: Glacier
          - Id: RetentionRule
            Status: Enabled
            ExpirationInDays: !Ref RetentionDays
            NoncurrentVersionExpirationInDays: !Ref RetentionDays
  # Create Bucket Policy for S3 Audit bucket
  S3AuditBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3AuditBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AWSBucketPermissionsCheck
            Effect: Allow
            Principal:
              Service:
                - cloudtrail.amazonaws.com
                - config.amazonaws.com
            Action: s3:GetBucketAcl
            Resource:
              - !Sub "arn:aws:s3:::${S3AuditBucket}"
          - Sid: AWSBucketDelivery
            Effect: Allow
            Principal:
              Service:
                - cloudtrail.amazonaws.com
                - config.amazonaws.com
            Action: s3:PutObject
            Resource:
                    - Fn::Join:
                        - ""
                        -
                          - "arn:aws:s3:::"
                          - !Ref "S3AuditBucket"
                          - !Sub "/${AWSLogsS3KeyPrefix}/AWSLogs/*/*"

Outputs:
  BucketName:
    Description: Audit S3 bucket name
    Value: !Ref S3AuditBucket
  LoggingBucketName:
    Description: S3 Access Logging Bucket name
    Value: !Ref S3LoggingBucket
  AuditLogsS3KeyPrefix:
    Description: S3 Key prefix for storing the audit logs
    Value: !Ref AWSLogsS3KeyPrefix

AWSControlTowerSecurityResources

概要

Lambda実行用のロール(adminとreadonlyの2つ)

対象

下記のcontroltowerが有効化された単一リージョン
audit

cloudformation template
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the Cross-Account IAM Audit Roles for Audit Account

Parameters:
  ManagedResourcePrefix:
    Type: 'String'
    Description: 'Prefix for the managed resources'
  AuditAccountId:
    Type: 'String'
    Description: 'Audit account Id'
  LoggingAccountId:
    Type: 'String'
    Description: 'Logging account Id'

Resources:
  AdministrationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-AuditAdministratorRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSLambdaExecute
      Policies:
        - PolicyName: !Sub AssumeRole-${ManagedResourcePrefix}-AuditAdministratorRole
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub "arn:aws:iam::*:role/${ManagedResourcePrefix}-AdministratorExecutionRole"

  ReadOnlyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ManagedResourcePrefix}-AuditReadOnlyRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSLambdaExecute
      Policies:
        - PolicyName: !Sub AssumeRole-${ManagedResourcePrefix}-AuditReadOnlyRole
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub "arn:aws:iam::*:role/${ManagedResourcePrefix}-ReadOnlyExecutionRole"

  # AWSConfig Aggregator for Guardrail compliance
  GuardrailsComplianceAggregator:
    Type: AWS::Config::ConfigurationAggregator
    Properties:
      AccountAggregationSources:
        - AccountIds:
          - !Ref AuditAccountId
          - !Ref LoggingAccountId
          AllAwsRegions: true
      ConfigurationAggregatorName: !Sub ${ManagedResourcePrefix}-GuardrailsComplianceAggregator

Outputs:
  CrossAccountAdminRole:
    Description: Audit Administrator Role
    Value: !GetAtt 'AdministrationRole.Arn'
  CrossAccountReadOnlyRole:
    Description: Audit ReadOnly Role
    Value: !GetAtt 'ReadOnlyRole.Arn'

最後に

役に立った人がいれば褒めて欲しい。。。。
Organization管理は、自社の管理しか深く関わらず、
サンプル数が少ないので
他の詳しい人の考え方も聞きたいと思う事がよくあります。
何かあればコメント頂けると嬉しいです。

AWS fsx を作った後にやること

AWS fsxを作成した後、AWSコンソールから以外ですべき設定作業があります。

それがこちら。 docs.aws.amazon.com

概要をざっとまとめるとこちらです。

Best Practices

項目 機能概要 関連リンク
Data Deduplication データの重複を極力削減してくれる機能 link
quota データ要領の利用制限をADグループ等に対し設定できる機能 link
shadow copy メリット:ファイル/フォルダ単位でのリストアが簡単に可能
デメリット:FSXの一部の要領をshadow copy用に利用する
link
link2
Encryption in Transit クライアントから接続の際、暗号化通信を設定する link

上記はAWSが言うところのベストプラクティスです。 ただ、上記以外にもやっておいた方が良さげなことがあります。

おすすめ

項目 機能概要 関連リンク
DNS Alias fsxに別のfqdnを付与できます リストアとかした時に便利です
Deployment typeがSingle-AZ 1の時のみ利用可能なので注意です
link
Shared Folders tool 接続ユーザの一覧や、ファイルがロックされてしまった場合に解除できる。設定ではないが一回触っておいた方が良い link
cloudwatch Alarm 残りの容量が少ない場合通知設定をする link

設定してみる

では、上記を設定してみます。
まず環境変数を設定します。
全ての作業の前で、下記を実行してください。

環境変数設定

f:id:remmemento:20210119162739p:plain 下記を「Windows Remote PowerShell Endpoint」の部分に置き換えて実行します。

#poweshell endpointを設定する
$FSxWindowsRemotePowerShellEndpoint="amznfsxxxxxxxxxx.fsx-test.local"

Data Deduplication(データ重複削減)

機能概要

この機能が有効化されていると、
定期的にバックグラウンドで、データ重複を検知し、
ディスクの要領を削減してくれます。

設定方法

# depulicationを有効化
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Enable-FsxDedup }

有効化するとデフォルトで良い感じにスケジュール実行してくれるそうです。
スケジュールを明確に指定する方法もあるようです。 docs.aws.amazon.com

Quota

機能概要

fsx本来要領に対しquota(制限)を設定できる機能です。
具体的には、ADの特定グループと、制限値を設定できるため、
ファイルサーバ的に利用している場合に、
特定のグループの人はxxGBまで、と要領制限を付けたい場合に利用すると良いと思います。

設定方法

## 記録のみしたい場合(Trackモード)
$QuotaLimit = 100000000
$QuotaWarningLimit = 100000
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Enable-FSxUserQuotas -Track -DefaultLimit $Using:QuotaLimit -DefaultWarningLimit $Using:QuotaWarningLimit }
## 要領を超えた場合に、強制的に書き込みを禁止したい場合(Enforceモード)
$QuotaLimit = 100000000
$QuotaWarningLimit = 100000
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Enable-FSxUserQuotas -Enforce -DefaultLimit $Using:QuotaLimit -DefaultWarningLimit $Using:QuotaWarningLimit }
## 現在のモードを確認する(enforceかtrackか)
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Get-FSxUserQuotaSettings }
## 特定のADのグループに制限を紐付けたい場合(例.グループ名:fsxservice)
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Set-FSxUserQuotas  -Domain "fsx-test.local" -Name "fsxservice" -Limit 10 -WarningLimit 5  }

shadow copy

機能概要

fsx内のデータを特定時点に復元したいとき、
バックアップからリストアが必要となります。
ただ、間違えてファイル上書きしてしまって、
特定のファイルだけ戻したいみたいなケースは良くあると思います。
そんな時、shadow copyを有効化しておくと、
特定のファイルだけ復元が可能です。
復元時の操作イメージは下記が分かりやすいと思います。
docs.aws.amazon.com

設定方法

#shadow copyを有効化
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Set-FsxShadowStorage -Default }    

#shadow copyのスケジュールを作成
$trigger1 = new-scheduledTaskTrigger -weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -at 06:00
$trigger2 = new-scheduledTaskTrigger -weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -at 18:00     
invoke-command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -scriptblock {
set-fsxshadowcopyschedule -scheduledtasktriggers $Using:trigger1,$Using:trigger2 -Confirm:$false }

# スケジュール確認
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Get-FsxShadowCopySchedule }    

Encryption in Transit

クライアントから接続の際、
通信を暗号化通信を強制します。

設定方法

#Enforcing Encryption in Transit
Invoke-Command -ComputerName $FSxWindowsRemotePowerShellEndpoint -ConfigurationName FSxRemoteAdmin -ScriptBlock { Set-FsxSmbServerConfiguration -EncryptData $True -RejectUnencryptedAccess $True -Confirm:$False}

DNS Alias

機能概要

通常、fsxが作成されるとID的に1つのFQDN(amznxxxxx.<ドメイン名>)が割り当てられます。
ただ、FQDNをリストアした場合や、 移行等の場合では、
固有のFQDNだと、設定変更が発生して面倒かと思います。
DNS Aliasを利用すると別のDNS名をつけることが可能です。

設定方法

まず、AWSコンソールで「DNS Alias」から指定したいエイリアス名を入力します。 f:id:remmemento:20210119020415p:plain その後、ドメイン参加のサーバからpowershell側で設定を追加します。
ドキュメントのサンプルは何回も環境変数をsetして
面倒だったため、まとめています。

$ALIAS = "fsx.fsx-test.local"
$FileSystemDnsName = "amznfsxjlrqzujy.fsx-test.local"

Install-WindowsFeature RSAT-AD-PowerShell

## Find SPNs for original file system's AD computer object
SetSPN /Q ("HOST/" + $ALIAS)
SetSPN /Q ("HOST/" + $ALIAS.Split(".")[0])

## Delete SPNs for original file system's AD computer object
$Alias = $ALIAS
$FileSystemHost = (Resolve-DnsName ${FileSystemDnsName} | Where Type -eq 'A')[0].Name.Split(".")[0]
$FSxAdComputer = (Get-AdComputer -Identity ${FileSystemHost})

SetSPN /D ("HOST/" + ${Alias}) ${FSxAdComputer}.Name
SetSPN /D ("HOST/" + ${Alias}.Split(".")[0]) ${FSxAdComputer}.Name

## Set SPNs for FSx file system AD computer object
$FSxDnsName = $FileSystemDnsName
$FileSystemHost = (Resolve-DnsName $FSxDnsName | Where Type -eq 'A')[0].Name.Split(".")[0]
$FSxAdComputer = (Get-AdComputer -Identity $FileSystemHost)

Set-AdComputer -Identity $FSxAdComputer -Add @{"msDS-AdditionalDnsHostname"="$Alias"}
SetSpn /S ("HOST/" + $Alias.Split('.')[0]) $FSxAdComputer.Name
SetSpn /S ("HOST/" + $Alias) $FSxAdComputer.Name

## Verify SPNs on FSx file system AD computer object
$FileSystemHost = (Resolve-DnsName ${FSxDnsName} | Where Type -eq 'A')[0].Name.Split(".")[0]
$FSxAdComputer = (Get-AdComputer -Identity ${FileSystemHost})
SetSpn /L ${FSxAdComputer}.Name

続いてDNS設定です。
こちらはADが動いているサーバで実行しました。
ドキュメント内のコマンドでうまくいかない点が
あったため一部修正しています。 f:id:remmemento:20210119162839p:plain Alias,FSxDnsNameを上記の部分に置き換えて実行します。

$Alias = "fsx.fsx-test.local"
$FSxDnsName = "amznfsxjlrqzujy.fsx-test.local"

Install-WindowsFeature RSAT-DNS-Server
$AliasHost=$Alias.Split('.')[0]
$ZoneName=((Get-WmiObject Win32_ComputerSystem).Domain)
$DnsServerComputerName = (Resolve-DnsName $ZoneName -Type NS | Where Type -eq 'A' | Select -ExpandProperty Name)

Add-DnsServerResourceRecordCName -Name $AliasHost -ComputerName $DnsServerComputerName -HostNameAlias $FSxDnsName -ZoneName $ZoneName

GPOを設定する

意味的にはNTLMを利用せずKerberos認証を強制するよう
グループポリシーを設することのようです。
強制したくない場合もあると思いますので、
一部の例外も併せて設定できます。 gpmc.mscを開きます

f:id:remmemento:20210119105156p:plain 対象のOUでグループポリシーを作成しEditします。 f:id:remmemento:20210119105215p:plain Localポリシー内の下記を選択します
Restrict NTLM: Outgoing NTLM traffic to remote servers f:id:remmemento:20210119110242p:plain 続いてLocalポリシー内の下記を選択します
Restrict NTLM: Add remote server exceptions for NTLM authentication
例外を指定したい場合はここで入力します。 f:id:remmemento:20210119110347p:plain

上記が完了したら、エイリアス名で正しく名前解決ができることを確認します。

PS C:\Users\Administrator> nslookup fsx.fsx-test.local
Server:  localhost
Address:  ::1

Name:    amznfsxjlrqzujy.fsx-test.local
Address:  172.30.5.102
Aliases:  fsx.fsx-test.local

その後、fsxに接続し、成功すれば完了です

net use z: \\fsx.fsx-test.local\share

Shared Folders tool

機能概要

下記のようなケースで管理者が使うツールです。 こちらは設定しておくものでなく、
管理者が利用するケースがあると思いますので、
構築したタイミングで動作を試してみると良いと思います。

  • FSXへの接続セッションを強制で切る
  • ファイルが競合してうまく開かない等あったら解決できる

    利用方法

    fsmgmt.msc

f:id:remmemento:20210119131056p:plain

fsxのdns名を入力 f:id:remmemento:20210119131114p:plain

その後、セッションを終了させる、
ファイル競合を解決する操作は下記が分かりやすいです。
docs.aws.amazon.com

cloudwatch Alarm

下記参考にFreeStorageCapacity(空き容量)の通知ぐらいは
しておいた方が良いかと思いました。 docs.aws.amazon.com

下記をまとめて作成するcloudformaitonを下記ました。
自己所有のADを参照するFsx用の記述のため
MicrosoftADをご利用の場合は、その部分を書き換えが必要です。
あと、各種パラメータは読み替えてください。

  1. fsx
  2. security-group
  3. SNS
  4. cloudwatch
AWSTemplateFormatVersion: "2010-09-09"
Description: A templete for fsx for windows
Parameters:
  EnvPrefix:
    Type: String
    Default: "test"
  # KmsKeyId:
  #   Type: String
  StorageCapacity:
    Type: Number
    Default : 32
  VPCId:
    Type: AWS::EC2::VPC::Id
    Default: vpc-xxxxxxxxx
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Default: subnet-xxxxxxxxxx
  # ActiveDirectoryId:
  #   Type: String
  #   Default: d-96671b8248
  DailyAutomaticBackupStartTime:
    Type: String
    Default: "16:00"
  DeploymentType:
    Type: String
    Default: SINGLE_AZ_2
    # Default: MULTI_AZ_1
  ThroughputCapacity:
    Type: Number
    Default : 8
  WeeklyMaintenanceStartTime:
    Type: String
    Default: "6:17:00"
    
  OnpreDNS:
    Type: CommaDelimitedList
    Default: xxx.xxx.xxx.xxx
  OnpreDomainName:
    Type: String
    Default: fsx-test.local
  FileSystemAdministratorsGroup:
    Type: String
    Default: FSXAdmins
  UserName:
    Type: String
    Default: fsxservice
  Password: 
    Type: String
    Default: xxxxxxxxxxxxxxxxxxxxxxxxxx
    NoEcho: True
  OrganizationalUnitDistinguishedName:
    Type: String
    Default: "OU=FileSystems,DC=fsx-test,DC=local"
  FSXAllowedRange:
    Type: String
    Default: 172.30.0.0/16

  ThresholdFreeStorageCapacity:
    Type: Number
    Default: 3221225472

  endpointEmail:
    Type: String
    Default: 'xxxxxxxxxxxxxx@xxxxxx.com'   


Resources :
  fsx:
    Type: AWS::FSx::FileSystem
    Properties: 
      # KmsKeyId: kms
      FileSystemType: WINDOWS
      SecurityGroupIds: 
        - !Ref sg
      StorageCapacity: !Ref StorageCapacity
      StorageType: SSD
      # StorageType: HDD
      SubnetIds: !Ref SubnetIds
      WindowsConfiguration: 
        # ActiveDirectoryId: !Ref ActiveDirectoryId
        AutomaticBackupRetentionDays: 7
        CopyTagsToBackups: true
        DailyAutomaticBackupStartTime: !Ref DailyAutomaticBackupStartTime
        DeploymentType: !Ref DeploymentType
        # PreferredSubnetId: String
        SelfManagedActiveDirectoryConfiguration: 
          DnsIps: !Ref OnpreDNS
          DomainName: !Ref OnpreDomainName
          FileSystemAdministratorsGroup: !Ref FileSystemAdministratorsGroup
          OrganizationalUnitDistinguishedName: !Ref OrganizationalUnitDistinguishedName
          Password: !Ref Password
          UserName: !Ref UserName
        ThroughputCapacity: !Ref ThroughputCapacity
        WeeklyMaintenanceStartTime: !Ref WeeklyMaintenanceStartTime

  sg:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: !Sub ${EnvPrefix}-sg
      GroupName: !Sub ${EnvPrefix}-sg
      VpcId: !Ref VPCId
      SecurityGroupIngress:
        -
          CidrIp: !Ref FSXAllowedRange
          FromPort: 445
          ToPort: 445
          IpProtocol: tcp
        -
          CidrIp: !Ref FSXAllowedRange
          FromPort: 5985
          ToPort: 5985
          IpProtocol: tcp
      Tags:
        -
          Key: Name
          Value: FSX

  CloudWatchAlarmFreeStorageCapacity:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: 'true'
      ComparisonOperator: LessThanOrEqualToThreshold 
      EvaluationPeriods: '1'
      MetricName: FreeStorageCapacity
      Namespace: AWS/FSx
      AlarmName: !Sub ${EnvPrefix}FreeStorageCapacity
      Period: '300'
      Statistic: Average
      Threshold: !Ref ThresholdFreeStorageCapacity
      AlarmActions:
        - !Ref SNSNotif
      Dimensions:
        -
          Name: FileSystemId
          Value: !Ref fsx

  SNSNotif:
      Type: AWS::SNS::Topic
      Properties:
        DisplayName: !Sub ${EnvPrefix}Topic
        Subscription:
          -
            Endpoint: !Ref endpointEmail
            Protocol: email
        TopicName: !Sub ${EnvPrefix}TopicTicket          

AWS fsx for windows (自己所有AD)の事前のAD設定メモ

FSXを自己所有のADで作成する際、AD側で何をすれば良いのかが AWSのドキュメントを見ても、いまいち分かりにくかったです。 docs.aws.amazon.com aws.amazon.com 上記を見ながら作業をしてみたところ正常動作でき、 作業をより分かりやすく残しておきたいと思い、 備忘メモとしてブログ化します。

ADの事前設定

ADでOU、グループ、ユーザを作成

まず、AD側の手順として、下記を作成します。

設定項目 設定値
OU FileSystems
UserName fsxservice
FileSystemAdministratorsGroup FSXAdmins
Password 適当

「dsa.msc」を開く f:id:remmemento:20210119005924p:plain OUを「FileSystems」で作成します。 f:id:remmemento:20210119005945p:plain

FileSystemsの中にユーザ、グループを作成します f:id:remmemento:20210119010841p:plain f:id:remmemento:20210119010821p:plain

その後、上記で作成したOUに権限を移譲します。
作成したOUで右クリックDelegate Controlを選択 f:id:remmemento:20210119010105p:plain

「FSXAdmins」を指定 f:id:remmemento:20210119010133p:plain

「Create a custom task to delegate」を選択します。 f:id:remmemento:20210119012129p:plain

「Only the following objects in the folder」を選択し
「Computer objects」を選択します。
下記2つを選択します。

  • 「Create selected objects in this folder」

  • 「Delete selected objects in this folder」 f:id:remmemento:20210119011043p:plain

さらに次の画面で下記を選択します。

  1. Reset Password
  2. Read and write Account Restriction
  3. Validated write to DNS host name
  4. Validated write to service principal name

f:id:remmemento:20210119012328p:plain

以上でAD側の作業は完了です。

fsxを作成する

上記が作成できましたら、fsxを作成します。 cloudformationを作りました。

AWSTemplateFormatVersion: "2010-09-09"
Description: A templete for fsx for windows
Parameters:
  EnvPrefix:
    Type: String
    Default: "test"
  # KmsKeyId:
  #   Type: String
  StorageCapacity:
    Type: Number
    Default : 32
  VPCId:
    Type: AWS::EC2::VPC::Id
    Default: vpc-xxxxxxxxxxx
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Default: subnet-xxxxxxxxxxx
  DailyAutomaticBackupStartTime:
    Type: String
    Default: "16:00"
  DeploymentType:
    Type: String
    Default: SINGLE_AZ_2
    # Default: MULTI_AZ_1
  ThroughputCapacity:
    Type: Number
    Default : 8
  WeeklyMaintenanceStartTime:
    Type: String
    Default: "6:17:00"
    
  OnpreDNS:
    Type: CommaDelimitedList
    Default: xxx.xxx.xxx.xxx
  OnpreDomainName:
    Type: String
    Default: fsx-test.local
  FileSystemAdministratorsGroup:
    Type: String
    Default: FSXAdmins
  UserName:
    Type: String
    Default: fsxservice
#本当はパラメータストア等を参照するのが良いです
  Password: 
    Type: String
    Default: xxxxxxxxxxxxxx
    NoEcho: True
  OrganizationalUnitDistinguishedName:
    Type: String
    Default: "OU=FileSystems,DC=fsx-test,DC=local"
  FSXAllowedRange:
    Type: String
    Default: xx.xx.xx.xx/xx
   

Resources :
  fsx:
    Type: AWS::FSx::FileSystem
    Properties: 
      # BackupId: String
      # KmsKeyId: kms
      # LustreConfiguration: 
      #   LustreConfiguration
      FileSystemType: WINDOWS
      SecurityGroupIds: 
        - !Ref sg
      StorageCapacity: !Ref StorageCapacity
      StorageType: SSD
      # StorageType: HDD
      # The StorageCapacity specified is not supported. Storage capacity for HDD must be no less than 2000
      SubnetIds: !Ref SubnetIds
      WindowsConfiguration: 
        # ActiveDirectoryId: !Ref ActiveDirectoryId
        AutomaticBackupRetentionDays: 7
        CopyTagsToBackups: true
        DailyAutomaticBackupStartTime: !Ref DailyAutomaticBackupStartTime
        DeploymentType: !Ref DeploymentType
        # PreferredSubnetId: String
        SelfManagedActiveDirectoryConfiguration: 
          DnsIps: !Ref OnpreDNS
          DomainName: !Ref OnpreDomainName
          FileSystemAdministratorsGroup: !Ref FileSystemAdministratorsGroup
          OrganizationalUnitDistinguishedName: !Ref OrganizationalUnitDistinguishedName
          Password: !Ref Password
          UserName: !Ref UserName
        ThroughputCapacity: !Ref ThroughputCapacity
        WeeklyMaintenanceStartTime: !Ref WeeklyMaintenanceStartTime

  sg:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: !Sub ${EnvPrefix}-sg
      GroupName: !Sub ${EnvPrefix}-sg
      VpcId: !Ref VPCId
      SecurityGroupIngress:
        -
          CidrIp: !Ref FSXAllowedRange
          FromPort: 445
          ToPort: 445
          IpProtocol: tcp
        -
          CidrIp: !Ref FSXAllowedRange
          FromPort: 5985
          ToPort: 5985
          IpProtocol: tcp
      Tags:
        -
          Key: Name
          Value: FSX

AWS StepFunctions Lambdaを利用する時のTips

Japan APN Ambassador Advent Calendarqiita.com

2020 8 日目のエントリです。
先日、会社でStepFunctionsの利用方法や、
Tipsを説明することがありました。
良い機会なので記事に書いてみようと思いました。

Stepfunctions概要

docs.aws.amazon.com サーバレスのサービスを組合せて利用する際の
オーケストレーションのサービスです。
具体例をみると分かりやすいかもしれません。
下記の図の各NodeがLambda、SQS等、 各種AWSのサービスを表しており、
それらの処理を組み合わせて定義することが可能です。 f:id:remmemento:20201208221953p:plain

Tips

利用し始めるとStepFunctionsならではの
機能や制限があることに後から気付き、
処理を作り直すことが良くありました。
事前に知っておきたかったことを 会社の後輩に伝えたところ割と喜んでくれたので書いていきます。

例外処理、再実行レベルを考慮する

上述のようにStepFunctionsでは
複数のLambdaを組み合わせることができます。
StepfunctionsでもLambda実行時のエラー考慮が必要になります。
Lambdaにはデッドレターキューというエラーの処理方法がありますが、
StepFunctionsのときは、代わりにStepFunctions独自のエラーハンドリングを利用すると便利です。

外部環境により発生した例外の場合

Lambdaでは(コードが正しくても)稀に実行が失敗することがあります。
レート制限エラーや、ネットワーク等の影響による一時的なエラーです。
このような場合は、数秒おいて再実行すれ成功する可能性が高いため
StepfunctionsのRetry機能を利用するのが良いと思います。
docs.aws.amazon.com 具体的には、下記のように特定のLambdaの処理に下記のように
Retryを設定することで、StepfunctionsがLambdaを再実行してくれます。

"Retry": [ {
   "ErrorEquals": [ "States.Timeout" ],  #このエラーのときに
   "IntervalSeconds": 3,    #インターバル3秒で
   "MaxAttempts": 2,        #最大2回再試行
   "BackoffRate": 1.5         #再実行の実行回数が増える毎にインターバルの秒数を増やす設定値
} ]

その他の例外の場合

上記以外ですと、 StepFunctionsのcatch機能を利用するのが良いと思います。
例外の内容により、次に実行する処理(Lambda)を制御できます。
docs.aws.amazon.com

具体的には、下記のように特定のLambdaの処理に下記のように
Catchを設定することで、StepfunctionsがLambdaがエラーだったときの ハンドリングをしてくれます。

 "Catch": [ {
            "ErrorEquals": ["States.Timeout"],  #このエラーのときは
            "Next": "fallback"    #次にfallbackの処理を実行する
         } ],

上記機能を利用し、
Lambda内はエラー処理をごちゃごちゃ書かず
シンプルな状態を保つとメンテナンスしやすいです。

Lambda単位で冪等性を持つように作る

上述のように各Lambdaの再実行設定が容易なため、
Lambda単位で冪等性(何回実行しても同じ結果になる)を
持つようにしておくと
StepFunctionsで利用しやすいです。

Mapやループ処理の際、StepFunctionsを分割する

バッチ処理をStepFunctionsで組む場合、 大量のデータをループ処理または並列処理したいことがあります。

StepFunctionsでは、下記のようにループ処理を書くことや f:id:remmemento:20201208231458p:plain
下記のような並列処理(MAP)の記載が可能です。 f:id:remmemento:20201208231557p:plain

ただし、StepFunctionsでは状態間の
データの受け渡し容量が262,144bytesと制限されています。
大量のデータをやりとりする場合は、この制限を超えてしまいます。
f:id:remmemento:20201208231829p:plain StepFunctionsではデータが制限を超える場合、S3等を利用し、
一時的にデータを別場所に保管することが推奨されています。

ただし、S3を利用しデータを受渡すると、
上記のような、ループや並列処理の記述ができません。

そこで、私の場合はStepFunctionsを2つに分けました。 f:id:remmemento:20201208232643p:plain 3つ目の処理の中で、大量データを取得し、
データ毎に別のStepFunctionsを実行することで
並列処理が可能でした。 将来的にデータ量が拡張し、制限を超える場合は、 このような記述も良いと思います。

StepFunctionsの実行時の引数を活用する

Stepfunctionsのフロー図を作成すると、
極力それを使いまわしたいと思います。
例えば特定条件のリソースを停止する処理を作成するとします。
毎月末に対象を通知し、毎月初に停止処理を実行する場合
停止をするかしないか以外は、ほぼ同じ処理になります。

そんなとき、StepFunctionsの実行時の引数で処理を分岐すると便利でした。
具体的にはStepFunctions実行時の引数として変数を渡し
その変数によって処理を分岐するイメージです。 f:id:remmemento:20201209000017p:plain

さいごに

Japan APN Ambassador Advent Calendarの
記事を書かせて頂き大変光栄でした!
会社を超えて、日々前向きな方達と交流が持てるのは
本当にありがたいです。 コロナ影響が早くなくなって、
もっとリアルでもお会いできるのを楽しみにしています。

AWS ClientVPNの構成図 7パターン

AWSのClientVPNには認証方式が複数あります。
認証方式により、構成やUXが微妙に変わり、その質問をいただくことが多いため、
ユーザ視点でどんな感じになるのかを、7パターンで整理してみたいと思います。
これ以外にも構成パターンはあると思いますが、それらは応用編で、
もし機会があればブログ化してみたいと思います。

構成は、認証方式に依存する形となります。
紹介するパターンは下記です。

  1. ADオンプレで認証
  2. AD(AWS managed)で認証
  3. SAML(Okta)で認証
  4. ADオンプレ+証明書で認証
  5. AD(AWS managed)+証明書で認証
  6. SAML(Okta)+証明書で認証
  7. 証明書のみで認証

(1)ADオンプレで認証

概要

こんな方向けです。

  • 既存のオンプレADを認証情報として利用したい
  • 認証はADユーザ/PASSのみでOK

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. ADコネクタを通して、ClientVPNとオンプレADでユーザ情報が連携されます
  3. ADのユーザ情報で認証が実施されます
  4. ClientVPNが接続可能となります。 クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  5. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  6. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

利用イメージ

ユーザが接続する時は下記の操作イメージです。
AWSが提供するClientVPNのソフトを起動します。
f:id:remmemento:20201029221740p:plain
ユーザ名、パスワードを入力します
f:id:remmemento:20201029222039p:plain 認証に通ると接続されます。

運用考慮点

ADとclientVPNの接続許可設定を連動させることができます 。
(例:特定のOUの人が、xx.xx.xx.xx/xxへの疎通が可能)
その設定を変更する際は、ADとClientVPNの設定を変更する必要があります

(2)AD(AWS managed)で認証

概要

こんな方向けです。

  • AWSのマネージドのADを認証情報として利用したい
  • 認証はADユーザ/PASSのみでOK

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. ADのユーザ情報で認証が実施されます
  3. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  4. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  5. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

運用考慮点

(1)のパターンと同様です。

利用イメージ

(1)のパターンと同様です。

(3)SAML(Okta)で認証

概要

こんな方向けです。

  • ID管理サービスを認証情報として利用したい(ADと紐付け不要、ADの管理したくない場合)
  • 認証はID管理サービスのログインでOK

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. ID管理サービス(Okta)のユーザ情報で認証が実施されます
  3. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  4. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  5. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

利用イメージ

ユーザが接続する時は下記の操作イメージです。
AWSが提供するClientVPNのソフトを起動します。
f:id:remmemento:20201029222555p:plain Oktaでの認証を求められるためid/passでログインします。
f:id:remmemento:20201029222613p:plain Oktaの認証に通るとclientVPNに接続可能となります。

利用イメージ(ユーザ管理)

Oktaでのユーザ管理は下記イメージです。
私も初めてOktaを利用したのですが、特に迷うことなく直感的に操作できました。 f:id:remmemento:20201029223212p:plain ユーザ管理画面です。 f:id:remmemento:20201029223109p:plain ユーザ登録画面です。

(4)ADオンプレ+証明書で認証

概要

こんな方向けです。

  • 既存のオンプレADを認証情報として利用したい
  • 認証はADユーザ/PASSとクライアント証明書による2要素の認証をしたい

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. クライアントの持つ証明書が正しいことを確認します
  3. ADコネクタを通して、ClientVPNとオンプレADでユーザ情報が連携されます
  4. ADのユーザ情報で認証が実施されます
  5. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  6. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  7. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

運用考慮点

(1)に加えて証明書、認証局の管理が必要となります。
クライアント証明書を紛失した場合や、追加の証明書を発行したい場合等、
一般的な認証局の運用が発生します。
また、クライアント証明書を利用者に配布する必要があり、 証明書周りの運用が割と手間です。

利用イメージ

(1)のパターンと同様です。

(5)AD(AWS managed)+証明書で認証

概要

こんな方向けです。

  • AWSのマネージドのADを認証情報として利用したい
  • 認証はADユーザ/PASSとクライアント証明書による2要素の認証をしたい

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. ADのユーザ情報で認証が実施されます
  3. クライアントの持つ証明書が正しいことを確認します
  4. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  5. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  6. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

運用考慮点

(1)に加えて証明書、認証局の管理が必要となります。
クライアント証明書を紛失した場合や、追加の証明書を発行したい場合等、
一般的な認証局の運用が発生します。
また、クライアント証明書を利用者に配布する必要があり、 証明書周りの運用が割と手間です。

利用イメージ

(1)のパターンと同様です。

(6)SAML(Okta)+証明書で認証

概要

こんな方向けです。

  • ID管理サービスを認証情報として利用したい(ADと紐付け不要)
  • 認証はID管理サービスのユーザ/PASSとクライアント証明書による2要素の認証をしたい

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. ID管理サービス(Okta)のユーザ情報で認証が実施されます
  3. クライアントの持つ証明書が正しいことを確認します
  4. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  5. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  6. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

運用考慮点

(3)に加えて証明書、認証局の管理が必要となります。
クライアント証明書を紛失した場合や、追加の証明書を発行したい場合等、
一般的な認証局の運用が発生します。
また、クライアント証明書を利用者に配布する必要があり、 証明書周りの運用が割と手間です。

利用イメージ

(3)のパターンと同様です。

(7)証明書のみで認証

概要

こんな方向けです。

  • 認証はクライアント証明書のみでOK

処理の流れ

  1. クライアントPCがClientVPNのEndpointに接続します
  2. クライアントの持つ証明書が正しいことを確認します
  3. ClientVPNが接続可能となります。クライアントPCはVPC内にあるENI-NATを通じてVPCやオンプレと通信が可能となります
  4. VPC内のEC2と疎通が可能となります(RouteTableとSGの設定を忘れずに)
  5. オンプレのサーバと疎通が可能となります(サーバ側の疎通許可設定を忘れずに)

運用考慮点

証明書、認証局の管理が必要となります。
クライアント証明書を紛失した場合や、追加の証明書を発行したい場合等、
一般的な認証局の運用が発生します。
また、クライアント証明書を利用者に配布する必要があり、 証明書周りの運用が割と手間です。

利用イメージ

特にユーザ/パスワードを入力することなく接続が可能です

どのパターンが良いのか

まず最初に認証がユーザ/パスワードのみで良いかを考えると良いかと思います。
ユーザ/パスワードのみの認証で十分な場合には(1)(2)(3)が候補です。
(1)(2)(3)は既存でADを利用しているかどうかで決めると良いと思います。

Enterpriseでセキュリティが厳しい場合は、
ユーザ/パスワードと証明書による認証で(4)(5)(6)が候補です。
(4)(5)(6)は既存でADを利用しているかどうかで決めると良いと思います。

(7)は現実的に利用する方は少ないと思います。

上記をまとめるにあたり、初めてOktaを利用してみたのですが、
個人的にはOktaがかなり使いやすく、Oktaおすすめです。
小規模の会社で、試しにClientVPNを利用してみるぐらいでしたら
パターン(3)が良いのではと思います。
証明書は、セキュリティ強度は上がると思うのですが、
人件費等の作業コストがかかることを考えると
Oktaのようなサービス利用料の方が
トータルで安くなるのかと思います。