AWS Config(3)アクセスキーの管理と自動削除

アクセスキーの適切な管理

CIS AWS Foundations Benchmark というセキュリティガイドラインが公開されており、このガイドラインは、AWSアカウントをセキュアに保つために必要なAWSのセキュリティ設定を集めたベストプラクティス集として活用できる。

この CIS AWS Foundations Benchmarkでは、アクセスキーの取り扱いについて以下のように定めている。

  • 1.3 90 日間以上使用されていない認証情報は無効にします
  • 1.4 アクセスキーは 90 日ごとに更新します

この CIS AWS Foundations Benchmark に 準拠していないアクセスキー を CloudWatch Events と Lambda を用いて 自動削除 するために以下の設定を行う。

  1. 上記のポリシーに準拠しているか AWS Config を用いて定期的にチェックを行う
  2. 非準拠であった場合には、CloudWatch Events が Lambdaを自動起動する
  3. Lambda が非準拠のアクセスキーを削除する

1. AWS Configの有効化

AWS Configを有効化する手順については、こちら。

2. AWS Configを用いた定期チェック

アクセスキーのチェックには、あらかじめAWS Configに用意されている access-keys-rotated マネージドルールを使用する。CIS AWS Foundations Benchmark で奨励されているアクセスキーの有効期限は、マネージドルール内の InputParameters で設定する。アクセスキーがこの条件を満たしていない場合、このリソースはルールに 非準拠(NON_COMPLIANT) であると判定される。

なお、この Config Rule を設定する前に ConfigurationRecorder を生成しておく必要がある。そこで、DependsOn 属性に ConfigurationRecorder リソースを設定している。

Resources:
  ConfigIamAccessKeysRotated:
    DependsOn:
      - ConfigConfigurationRecorder
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: access-keys-rotated
      Description: アクティブなアクセスキーが、maxAccessKeyAge で指定された日数内にローテーションされるかどうかを確認します。
      InputParameters:
        maxAccessKeyAge: 90
      Source:
        Owner: AWS
        SourceIdentifier: ACCESS_KEYS_ROTATED

3. CloudWatch Events を用いた Lambda の自動実行

Configルールに 非準拠(NON_COMPLIANT)となった場合に、これを修復するLambdaを発火させるためのトリガとして、CloudWatch Events を設定する。

Resources:
  CloudWatchEventsForConfigIamAccessKeysRotated:
    Type: 'AWS::Events::Rule'
    Properties: 
      Description: CloudWatch Events about Config IAM Access Keys Rotated.
      EventPattern:
        source:
          - aws.config
        detail-type: 
          - Config Rules Compliance Change
        detail:
          messageType:
            - ComplianceChangeNotification
          newEvaluationResult:
            complianceType:
              - NON_COMPLIANT
      Name: AWS_Config
      State: ENABLED
      Targets:
        - Arn: !GetAtt LambdaDeleteExpiredAccessKeys.Arn
          Id: lambda

4. Lambda を用いた アクセスキー の削除

Lambdaが受け取るConfigから通知されたメッセージには、期限切れのアクセスキーを持つIAMユーザの情報が含まれる。そこでLambdaは、このユーザが持つアクセスキーのうち、有効期限を超過したアクセスキーのIDを探し当て、DeleteAccessKey API を用いてこれを削除する。

また、AWS::Lambda::Permission リソースタイプを用いて、前述のCloudWatch Eventsが、このLambdaを実行可能とする権限を設定する。

Resources:
  LambdaDeleteExpiredAccessKeys:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import boto3
          import datetime
          import time
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              logger.info(str(event))
            
              if 'detail' in event:
                  detail = event['detail']
                  if 'configRuleName' in detail:
                      # access-keys-rotated
                      if detail['configRuleName'] == 'access-keys-rotated':
                          iam = boto3.client('iam')
                          users = iam.list_users()
                          for user in users['Users']:
                              if detail['resourceId'] == user['UserId']:
                                  access_keys = iam.list_access_keys(
                                      UserName=user['UserName']
                                      )
                                  for access_key in access_keys['AccessKeyMetadata']:
                                      create_date = access_key['CreateDate'].timestamp()
                                      now = time.time()
                                      if now - create_date > 60*60*24*90:
                                          response = iam.delete_access_key(
                                              UserName=user['UserName'],
                                              AccessKeyId=access_key['AccessKeyId']
                                          )
      Description: 有効期限が過ぎたアクセスキーを削除します
      FunctionName: deleteExpiredAccessKeys
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt IAMRoleForLambda.Arn
      Runtime: python3.7
      Tags:
        - Key: !Ref TagKey
          Value: !Ref TagValue
      Timeout: 3
      TracingConfig:
        Mode: Active
  LambdaDeleteExpiredAccessKeysPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaDeleteExpiredAccessKeys
      Principal: events.amazonaws.com
      # DO NOT write 'SourceAccount' option.
      SourceArn: !GetAtt CloudWatchEventsForConfigIamAccessKeysRotated.Arn

以上で、CIS AWS Foundations Benchmark に非準拠のアクセスキーを削除することができた。

AWS Lambda(3)使用する上で注意すること

エントリポイントをコアロジックから分離

トリガを受ける関数と処理を行う関数を分離することで、単体テストが実施しやすくなり、また依存関係が完結となり関数のパフォーマンスも向上する。

再帰的な処理を記述しない

意図しない処理が発生する可能性があるため。

メモリ使用量を計測し適切なサイズに設定する

AWS CloudWatch LogsのMax Memory Usedを参照して最適なメモリサイズを設定する。

実行時間を計測し適切なタイムアウト値を設定する

同時実行数にも関連するので、実際の値を正確に把握する。

AWS Lambda(2)LambdaでKinesis Recordを取得する

Kinesis Process Records

Kinesis Event 発生毎にLambdaを実行し、Kinesis Recordを取得することが可能である。AWS マネジメントコンソールから関数を作成する場合は、kinesis-process-recordを選択。

Kinesis関数の設定

Stream名など必要事項を記入しプログラムをアップロードすることでLambdaを実行することが可能となる。

lambda_kinesis_function_2

Eclipseで開発する場合は、EclipseのAWSプラグインから直接アップロードや設定を行うことも可能である。Eclipseプロジェクトを右クリックして、「Amazon Web Services」から「Upload function to AWS Lambda…」を選択、「アップロードするリージョン」と「関数名」を指定し、

Kinesis関数の設定

「Description」や「Role」、プログラム一式を格納する「S3バケット」を指定して「Finish」を押すとEclipseで作成したLambda関数がAWS上にアップロードされる。

Eclipse設定

JavaによるLambda関数の実装

RequestHandler<KinesisEvent, Object>を実装することで、Kinesisからデータを取得し処理を行うことが可能となる。1回の処理で複数のKinesis Recordを取得するのでfor文で廻してそれぞれのRecordの値を取得することができる。Kinesis Recordには、ApproximateArrivalTimestampというRecord入力時に自動付与されるタイムスタンプが存在し、この値を取得するためのgetApproximateArrivalTimestamp()というメソッドも用意されているが、現時点では値の取得に対応していおらず、このメソッドを実行してもnullしか返えらないので注意が必要である。

public class KinesisEventHandler implements RequestHandler<KinesisEvent, Object> {

    @Override
    public Object handleRequest(KinesisEvent input, Context context) {

        context.getLogger().log("Input: " + input);

        for(KinesisEventRecord rec : input.getRecords()) {
        	// Recordを取得
        	Record record = rec.getKinesis();
        	// JSONを取得
        	byte[] byteArray = new byte[record.getData().remaining()];
        	record.getData().get(byteArray);
        	String json = new String(byteArray);
        	// タイムスタンプが取得できない
        	String timestamp = rec.getKinesis().getApproximateArrivalTimestamp();
        }
    }
}

Lambdaのスロットリング

Lambdaは、1つの関数につき1秒あたり同時処理数100でスロットリングされている。また、上限値の引き上げを申請しても1秒あたり同時接続数1000程度が限界のようである。したがって、Kinesisに大量のRecordが入力された場合はLambdaの上限を超えてしまいRecordを取りこぼしてしまう可能性がある。Kinesis Recordを確実に取得し処理する必要がある場合は、EC2上でKinesis Client Libraryを用いてKinesis Appを実装する必要がある。

AWS Lambda(1)Lambdaの概要

Lambdaとは

AWS Lambdaは、イベントをトリガにクラウド上で独自のコードを実行させるサービス。EC2インスタンス等の管理が不要で負荷に合わせて自動でスケールアウトすることのできるフルマネージドのサービスである。100ミリ秒単位の処理時間で従量課金される。例えば、画像がアップロードされたタイミングでサムネイル画像を生成したり、DynamoDBへの書き込みタイミングプッシュ通知を行うなどをLambdaを用いて実現できる。イベントの発生元となるAWSリソースは、S3, Kinesis, DynamoDB, Cognitoなどで、Pull/Pushの2種類のイベントモデルが存在する。

コードはブラウザ上で直接編集するか、Zip形式のファイルをアップロードすることで編集可能である。各種ライブラリはZipファイルに含めることで実行が可能となる。

データの永続化やS3やDynamoDBを使用する。/tmpも使用できる。

LambdaのIAM権限は、誰がファンクションを実行できるかを記述したInvocation Roleと、どのようなことを実行できるかを記述したExecution Roleの2種類が存在する。

コンテクスト

実行コンテナは、タイムアウト(Timeout)した場合や、”context.done()“が呼び出された場合(Controlled termination)、全ての処理を終了した場合(Default termination)、クラッシュや”process.exit()“した場合に処理を終了する。実行コンテナは、前回の処理からある程度の時間が経過していた場合は新規で作成されるが、前回使用したものを再利用することもある。context.done()を記述することで予想外の実行を防ぐことができる。

値 内容
context.succeed(Object result) ファンクションおよびコールバックが正常終了
context.fail(Object result) ファンクションおよびコールバックがエラー
context.done(String message, Object result) ファンクションが終了. messageに値が入力されているとエラー
awsRequestId ファンクション呼び出しID
logStreamName CloudWatch LogsのLogストリーム名
clientContext クライアントアプリおよびデバイスの情報
Identity CognitoのIdentity Provider情報