AWS-Backup

AWS Backup Audit ManagerとLambdaでバックアップのコンプライアンス違反を自動通知してみた

はじめに

こんにちは、omkです。
皆様、ちゃんとバックアップしてますか。私は最近ブログの下書きの保存を忘れて1件書き直しました。

Audit Managerとは

さて、少し前にAWS Backupで「AWS Backup Audit Manager」が利用できるようになりました。
リソースの保護が適切に行われているか基準(フレームワーク)を設けて評価することが出来ます。

リソースがバックアッププランで保護されているか、バックアップの頻度と保持期間などの基準でプランや復旧点、バックアップ対象となりえるリソースを評価します。


↑(全然準拠していない私の個人検証アカウントです)

AWS推奨のフレームワークだけでなく、自分でカスタムできるため、特定のリソースや特定のタグ付けがされているリソースなどに対象を制限できるため、環境毎に違うルールを設定することも可能です。

また、レポート機能ではフレームワークへの準拠状況を示すコンプライアンスレポートやバックアップや復元のジョブを示すバックアップアクティビティレポートがあります。
これらは1日1回自動でS3にレポートを出してくれます。

今回はコンプライアンスレポートを利用してリソースがコンプライアンスに準拠しているかを確認しますが、毎回コンソールで確認したりレポートをダウンロードして確認するのも面倒です。
なので、レポート出力時にLambdaに自動でチェックしてもらい、コンプライアンス違反があったときだけメールで通知することとします。

構成

こんな構成になります。
レポートにはコントロールコンプライアンスレポートを利用します。

やってみた

S3、Lambda、SNSはSAMで作ったのでテンプレートを置いておきます。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: python3.8
    Timeout: 3

Parameters:  
  LogPrefix:
    Type: String

  LogSuffix:
    Type: String

  MailAddr:
    Type: String

Resources:

  LambdaRole:
    Type: AWS::IAM::Role
    Properties: 
      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
        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
      Policies:
        - PolicyName: SNSPublishRole
          PolicyDocument: 
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: 
                  - 'sns:Publish'
                Resource: 
                  - !Ref AlarmTopic
        - PolicyName: S3BucketROAccess
          PolicyDocument: 
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: 
                  - "s3:GetObject"
                Resource: * 

  ReportBucket:
    Type: AWS::S3::Bucket

  AlarmTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: daily-backup-alarm-topic
      Subscription:
        - Protocol: email
          Endpoint: !Ref MailAddr

  DailyBackupChecker:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: daily-backup-checker/
      Handler: app.lambda_handler
      Role: !GetAtt LambdaRole.Arn
      Tracing: Active
      Architectures:
        - x86_64
      Environment:
        Variables: 
          AlarmTo: !Ref AlarmTopic
      Events:
        Backet:
          Type: S3
          Properties:
            Bucket: !Ref ReportBucket
            Events: s3:ObjectCreated:Put
            Filter:
              S3Key:
                Rules:
                - Name: prefix
                  Value: !Ref LogPrefix
                - Name: suffix
                  Value: !Ref LogSuffix

Outputs:
  BucketName:
    Description: "Make Audit Manager to put report into this bucket"
    Value: !Ref ReportBucket
  • S3バケットはBackup Audit Managerで利用するプレフィックスとサフィックス(.jsonを想定)に対してイベント通知を設定
  • Lambdaではイベントを元にS3のファイルを開き、コンプライアンスに準拠しているかを確認
  • 準拠していない場合はSNSでスタック作成時に設定したメールアドレスに対して通知

という流れです。

次にLambdaです。python3.8です。

import json
import urllib.parse
import os
import boto3

def lambda_handler(event, context):

    #print(event)

    # Open report file

    BucketName = event['Records'][0]['s3']['bucket']['name']
    ObjectKey = urllib.parse.unquote(event['Records'][0]['s3']['object']['key'])

    s3 = boto3.resource('s3')

    bucket = s3.Bucket(BucketName)
    obj = bucket.Object(ObjectKey)

    try:
        response = obj.get()

    except Exception as e:
        print("An error was ocurred when opening report file: %s/%s" % (BucketName,ObjectKey))
        print(e)
        return 

    report = json.loads(response['Body'].read().decode('utf-8'))

    # Check report if resources are compliant

    IsComlpiant = True
    for item in report['reportItems']:
        if item['controlComplianceStatus'] == "NON_COMPLIANT":
            IsComlpiant = False
            print("not comliant for %s of %s" % (item['controlName'],item['frameworkName']))

    if not IsComlpiant:
        # Notice the result

        sns = boto3.client('sns')

        topic = os.environ.get('AlarmTo')
        print("Alarm to topic: " + topic)
        subject = "AWS Backup Compliance Error from Audit Manager"
        message = "Some resources are not compliant for AWS Backup Controls.\nSee your AWS Backup console or CWLogs"

        try:
            sns.publish(
                TopicArn=topic,
                Subject=subject,
                Message=message
            )
        except Exception as e:
            print("An error was ocurred when publishing alarm to %s" % topic)
            print(e)

    return 

S3イベント通知からバケットのパスを割り出してレポートを読み込み、各コントロールの準拠状況を確認し、非準拠のコントロールがあれば通知します。SNSトピックのArnは環境変数「AlarmTo」に入れています。

S3イベントの形式はこんな感じです。

{
    "Records": [
        {
            "eventVersion": "2.1",
            "eventSource": "aws:s3",
            "awsRegion": "ap-northeast-1",
            "eventTime": "2021-11-17T11:16:52.753Z",
            "eventName": "ObjectCreated:Put",
            "userIdentity": {
                "principalId": "AWS:************:AWSBackup-AWSServiceRoleForBackupReports"
            },
            "requestParameters": {
                "sourceIPAddress": "**.**.**.**"
            },
            "responseElements": {
                "x-amz-request-id": "************",
                "x-amz-id-2": "*****************************************************"
            },
            "s3": {
                "s3SchemaVersion": "1.0",
                "configurationId": "********************",
                "bucket": {
                    "name": "*********************",
                    "ownerIdentity": {
                        "principalId": "*************"
                    },
                    "arn": "arn:aws:s3:::************************"
                },
                "object": {
                    "key": "compl/Backup/*************/ap-northeast-1/2021/11/17/controlecompliance_16_11_2021/CONTROL_COMPLIANCE_REPORT_controlecompliance_16_11_2021_2021-11-17T11%3A16%3A52.654Z.json",
                    "size": 2592,
                    "eTag": "*******************************",
                    "sequencer": "**************"
                }
            }
        }
    ]
}

コントロールコンプライアンスレポートの形式はこんな感じです。

{
  "reportItems": [
    {
      "accountId": "***************",
      "region": "ap-northeast-1",
      "frameworkName": "daily_backup_flamework",
      "frameworkDescription": "",
      "controlName": "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN",
      "controlComplianceStatus": "NON_COMPLIANT",
      "lastEvaluationTime": "2021-11-16T09:13:43.030Z",
      "numResourcesCompliant": 0,
      "numResourcesNonCompliant": 1,
      "controlScope": "",
      "controlParameters": "{\"resourceId\":\"i-**************\"}"
    }
  ]
}

あとはBakcup側でフレームワークとレポートを設定していきます。

フレームワークはコンパネからAWS推奨のものをそのまま利用します。
先程貼った画像のフレームワークができました。

次にレポートを設定していきます。
レポートプランに「コントロールコンプライアンスレポート」を選択し、プラン名と説明を入れます(コンパネが日本語の場合プラン名もデフォルト日本語ですが英数字とアンダーバーしか使えないので注意)。

フレームワークは先程作成したものを選択します。

レポート配信はファイル形式をJSONにし、バケット名をCFnスタックで作成されたもの、バケットプレフィックスをスタック作成時に設定したものを入力します。

あとは「アクセス許可をコピー」を選択したのち、「バケットポリシーを編集」を選択してS3のバケットポリシーでレポートの出力を許可します。

以上で「レポートプランを作成」を選択して完了です。

オンデマンドレポートを出力してみて動作を確認します。

CloudWatchLogs

not comliant for BACKUP_PLAN_MIN_FREQUENCY_AND_MIN_RETENTION_CHECK of AWS_Backup_Flamework
not comliant for BACKUP_RECOVERY_POINT_MANUAL_DELETION_DISABLED of AWS_Backup_Flamework
not comliant for BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN of AWS_Backup_Flamework
not comliant for BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN of daily_backup_flamework
Alarm to topic: arn:aws:sns:ap-northeast-1:***********:daily-backup-alarm-topic

メール

Some resources are not compliant for AWS Backup Controls.
See your AWS Backup console or CWLogs

--

できました。

おわりに

以上でBackupのルールに準拠しているかが簡単にわかるようになりました。
毎日見なくていいのは楽ですが、そもそものコンプライアンスの見直しも定期的に行う必要がありますね。

以上、お付き合いありがとうございました。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA