🐉

【CFn】公式サンプルテンプレからAWS Configの構成例を勉強する。

2022/10/28に公開

今回のスタックで作成するもの。

Amazon EC2ボリュームと、すべてのAmazon EC2ボリュームに必要なタグがあるかどうかを確認し、自動I/Oを有効にする AWSConfigルールを作成します。

元のテンプレートの場所

↓のコードブロックについて。

今回、Properties:以下の各項目について、「ドキュメント上の必須項目であるか否か」をコメントに記載しました。

・必須: はい→[必須]
・必須: いいえ→[必須項目ではない]
・必須: 条件付き→[条件付き必須項目]

あくまでもドキュメントで以下の3種に分類されているだけで、「必須: いいえ」だったからといって指定しなくてもデフォルト値で正常動作したり、よしなにやってくれる事を保証されている訳ではないようです。

「それがないと単独で確実にエラーが起きる訳ではないけど適宜必要になるからリソースやプロパティとの関連性を考えて設定してね」という事ではないかと思います。

この辺りは自分で作成する際も「通ってしまった時ヤバい設定」を最大限ドキュメントを読み込んでスクリーニングしながら、エラーを吐いたり作成がFailedしてくれるものは対応しながら経験を積んでいきたいと思います^^

sample.yml
AWSTemplateFormatVersion: 2010-09-09
Description: >-  #「>-」→は複数行テキストの記法のひとつ 詳しくは https://qiita.com/jerrywdlee/items/d5d31c10617ec7342d56
  AWS CloudFormation Sample Template Config: This template demonstrates the
  usage of AWS Config resources.  **WARNING** You will be billed for the AWS
  resources used if you create a stack from this template.

# ============== パラメータ ==============
Parameters: # パラーメータ(スタック作成者に入力・選択を求める項目を作成)

  Ec2VolumeAutoEnableIO: # 
    Type: String # 文字列
    Default: 'false' # デフォルト値 
    AllowedValues: # 許可する値は以下2つ
      - 'false'  
      - 'true'

  Ec2VolumeTagKey:
    Type: String # 文字列
    Default: CostCenter # デフォルト値 

# ============== リソース ==============
Resources: # リソース
# -------------- EBS --------------
  # EBSボリューム。
  Ec2Volume:
    Type: 'AWS::EC2::Volume' 
    Properties:
      AutoEnableIO: !Ref Ec2VolumeAutoEnableIO #[必須項目ではない] ボリュームがI/O操作に対して自動有効化されているかどうかを示す。ここではパラメータで作成者の選択を反映。
      Size: '5' #[条件付き必須項目] ボリュームのサイズ(GiB 単位)。スナップショットIDまたはボリュームサイズのいずれかを指定する必要有り。
      AvailabilityZone: !Select #[必須] ボリュームを作成するアベイラビリティーゾーン。
        - 0
        - !GetAZs ''
      Tags: #[必須項目ではない]
        - Key: !Ref Ec2VolumeTagKey # パラメータで入力した文字列を反映。
          Value: Ec2VolumeTagValue

# -------------- Config --------------
  # レコーダー
  ConfigRecorder:
    Type: 'AWS::Config::ConfigurationRecorder' # 「Configが設定変更を記録するAWSリソースタイプを記述」との事。
    Properties:
      Name: default #[必須項目ではない] 作成後名前変更不可。指定しない場合CFnが自動で生成する様子。
      RecordingGroup: #[必須項目ではない] 「Configでサポートされる前リソース、或いはリソースのリスト構成を記録するかどうかを示す」との事。
        ResourceTypes: ##[必須項目ではない] Configが設定変更を記録するAWSリソース。
          - 'AWS::EC2::Volume' # ここでは↑のEBSボリュームを指定。
      RoleARN: !GetAtt #[必須] 「指定した配信チャネルへの読み取りまたは書き込みリクエストを作成し、サポートされているAWSリソースの構成の詳細を取得するために使用。」との事。ARNで指定。
        - ConfigRole #↓のロール②の
        - Arn # ARNを取得して指定。

  # デリバリーチャンネル
  DeliveryChannel:
    Type: 'AWS::Config::DeliveryChannel' # 「S3バケットとSNSトピックに設定情報を配信するための配信チャネル」との事。
    Properties:
      ConfigSnapshotDeliveryProperties: #[必須項目ではない] Configが構成スナップショットをS3バケットに配信する頻度。
        DeliveryFrequency: Six_Hours #[必須項目ではない] 許容値:One_Hour | Six_Hours | Three_Hours | Twelve_Hours | TwentyFour_Hours
      S3BucketName: !Ref ConfigBucket #[必須]↓のS3バケットを指定。
      SnsTopicARN: !Ref ConfigTopic #[必須項目ではない] Configが設定変更に関する通知を送信するSNSトピックのARN。ここでは↓のSNSトピックを指定。

  # ルール①
  ConfigRuleForVolumeTags:
    Type: 'AWS::Config::ConfigRule' # AWSリソースが目的の設定に準拠しているかどうかを評価するためのConfigルール。
    Properties:
      InputParameters: #[必須項目ではない] ConfigルールのLambda関数に渡されるJSON形式の文字列。
        tag1Key: CostCenter
      Scope: #[必須項目ではない] ルールの評価をトリガーできるリソースを定義。1つ以上のリソースタイプ | 1つのリソースタイプと1つのリソースIDの組み合わせ | またはタグキーと値の組み合わせ を含めることが可能。
        ComplianceResourceTypes: #[必須項目ではない] ルールの評価をトリガーするAWSリソースのみのリソースタイプ。ComplianceResourceIdのリソースIDも指定する場合、指定できるタイプは1つだけになる。
          - 'AWS::EC2::Volume'
      Source: #[必須] ルールの所有者、ルール識別子、および関数が AWS リソースを評価する原因となる通知を提供」との事。
        Owner: AWS #[必須] 許容値:AWS | CUSTOM_LAMBDA | CUSTOM_POLICYAWS またはお客様(ユーザー)がConfigルールを所有および管理しているかどうかを示す。
        SourceIdentifier: REQUIRED_TAGS #[必須項目ではない] Configマネージド ルールの場合、リストから事前定義された識別子。
    DependsOn: ConfigRecorder # 指定のリソースの作成完了後にこのリソースを作成。ここでは↑のレコーダーを指定。

  # ルール②
  ConfigRuleForVolumeAutoEnableIO:
    Type: 'AWS::Config::ConfigRule'
    Properties:
      ConfigRuleName: ConfigRuleForVolumeAutoEnableIO #[必須項目ではない] 指定しない場合CFnが一意の物理IDを生成。
      Scope: #[必須項目ではない] 同上
        ComplianceResourceId: !Ref Ec2Volume #[必須項目ではない] ルールの評価をトリガーする唯一のAWSリソースのID。リソースIDを指定する場合は、ComplianceResourceTypesに対して1つのリソースタイプを指定する必要がある。
        ComplianceResourceTypes:  #[必須項目ではない]  同上
          - 'AWS::EC2::Volume'
      Source: #[必須] 同上
        Owner: CUSTOM_LAMBDA #[必須] 同上
        SourceDetails:  #[必須項目ではない] Configがルールに対してAWSリソースを評価する原因となるソースとメッセージタイプを提供。トリガータイプが定期的である場合には、Configがルールの評価を実行する頻度も提供。
          - EventSource: aws.config #[必須] 許容値:aws.config(のみ) AWSリソースを評価するためにConfigをトリガーするAWSサービスなどのイベントのソース。
            MessageType: ConfigurationItemChangeNotification #[必須] ルール評価を実行するためにConfigをトリガーする通知のタイプ。許容値:ConfigurationItemChangeNotification | ConfigurationSnapshotDeliveryCompleted | OversizedConfigurationItemChangeNotification | ScheduledNotification
        SourceIdentifier: !GetAtt #[必須項目ではない] 同上
          - VolumeAutoEnableIOComplianceCheck # ↓のLambda関数の
          - Arn # ARNを取得して指定。
    DependsOn: # 同上
      - ConfigPermissionToCallLambda # ↓のLambda関数と
      - ConfigRecorder # ↑のレコーダーを指定。

# -------------- S3 -------------- 
  # S3バケット
  ConfigBucket:
    Type: 'AWS::S3::Bucket' # 必須項目無し

# -------------- SNS -------------- 
  # トピック
  ConfigTopic: 
    Type: 'AWS::SNS::Topic' # 必須項目無し

  # トピックポリシー
  ConfigTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy' 
    Properties:
      PolicyDocument: #[必須] 指定されたSNSトピックに追加する権限を含むポリシードキュメント
        Id: ConfigTopicPolicy
        Version: 2012-10-17
        Statement:
          - Effect: Allow # 許可
            Principal: #プリンシパルは
              Service: config.amazonaws.com # AWS Config
            Action: 'SNS:Publish'
            Resource: '*'
      Topics: #[必須] ポリシーを追加するトピックのARN。
        - !Ref ConfigTopic #↑のSNSトピック
# -------------- Lambda --------------
  # Lambdaパーミッション
  ConfigPermissionToCallLambda:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName: !GetAtt  #[必須] 関数名(名前のみ | エイリアス有り) | 関数ARN(arn:aws:lambda:[regioon]〜関数名までフル) | 部分ARN(123456789012:function:my-function)形式が許容される。
        - VolumeAutoEnableIOComplianceCheck # ↓のLambda関数の
        - Arn # ARNを取得して指定。
      Action: 'lambda:InvokeFunction' #[必須] ↓のプリンシパルが関数で使用可能なアクション。
      Principal: config.amazonaws.com #[必須] 関数を呼び出すAWSサービスまたはアカウント。今回はconfig。

  # Lambda関数
  VolumeAutoEnableIOComplianceCheck:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code: #[必須] コード
        # (Node.js および Python)Lambda関数のソースコード。
        # ZipFileパラメータを使用してインラインで関数ソースを含める場合、CFnはそれをindexという名前のファイルに配置しzipしてデプロイパッケージを作成。
        # zipファイルは4MBを超えることは不可。
        ZipFile: !Join 
          - |+

          - - var aws  = require('aws-sdk');
            - var config = new aws.ConfigService();
            - var ec2 = new aws.EC2();
            - 'exports.handler = function(event, context) {'
            - '    compliance = evaluateCompliance(event, function(compliance, event) {'
            - '        var configurationItem = JSON.parse(event.invokingEvent).configurationItem;'
            - '        var putEvaluationsRequest = {'
            - '            Evaluations: [{'
            - '                ComplianceResourceType: configurationItem.resourceType,'
            - '                ComplianceResourceId: configurationItem.resourceId,'
            - '                ComplianceType: compliance,'
            - '                OrderingTimestamp: configurationItem.configurationItemCaptureTime'
            - '            }],'
            - '            ResultToken: event.resultToken'
            - '        };'
            - '        config.putEvaluations(putEvaluationsRequest, function(err, data) {'
            - '            if (err) context.fail(err);'
            - '            else context.succeed(data);'
            - '        });'
            - '    });'
            - '};'
            - 'function evaluateCompliance(event, doReturn) {'
            - '    var configurationItem = JSON.parse(event.invokingEvent).configurationItem;'
            - '    var status = configurationItem.configurationItemStatus;'
            - '    if (configurationItem.resourceType !== ''AWS::EC2::Volume'' || event.eventLeftScope || (status !== ''OK'' && status !== ''ResourceDiscovered''))'
            - '        doReturn(''NOT_APPLICABLE'', event);'
            - '    else ec2.describeVolumeAttribute({VolumeId: configurationItem.resourceId, Attribute: ''autoEnableIO''}, function(err, data) {'
            - '        if (err) context.fail(err);'
            - '        else if (data.AutoEnableIO.Value) doReturn(''COMPLIANT'', event);'
            - '        else doReturn(''NON_COMPLIANT'', event);'
            - '    });'
            - '}'
      Handler: index.handler #[必須項目ではない] 関数を実行するために Lambda が呼び出すコード内のメソッドの名前。
      Runtime: nodejs16.x #[必須項目ではない] 関数のruntimeの識別子。展開パッケージが .zipファイルアーカイブの場合、ランタイムが必要。※「nodejs」から変更。
      Timeout: '30' #[必須項目ではない] Lambdaが関数を停止する前に実行できる時間(秒単位)。デフォ3秒、最大許容値900秒。
      Role: !GetAtt #[必須] 関数の実行ロールのARN
        - LambdaExecutionRole #↓のロール①の
        - Arn # ARNを取得して指定。
# -------------- IAM --------------
# ロール①
  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument: #[必須] ロールに関連付けられる信頼ポリシー。
        Version: 2012-10-17
        Statement:
          - Effect: Allow # 許可する。
            Principal:
              Service:
                - lambda.amazonaws.com # Lambdaが
            Action:
              - 'sts:AssumeRole' # このロールを引き受ける事を。
      Policies: #[必須項目ではない] ロールに埋め込まれたインラインポリシードキュメント。
        - PolicyName: root #[必須] ポリシーを識別するフレンドリ名(ARNではない)。
          PolicyDocument: #[必須] 
            Version: 2012-10-17
            Statement:
              - Effect: Allow # 許可する。
                Action: 
                  - 'logs:*' # ログに関する全てのアクション。
                  - 'config:PutEvaluations' # 評価結果をConfigに配信するためにLambda関数によって使用されます。Configルールによって呼び出される全Lambda関数で必要。
                  - 'ec2:DescribeVolumeAttribute' # 指定ボリュームの指定属性を記述。
                Resource: '*' 
# ロール②
  ConfigRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument: #[必須] 同上
        Version: 2012-10-17
        Statement:
          - Effect: Allow # 許可する。
            Principal:
              Service:
                - config.amazonaws.com # Configが
            Action:
              - 'sts:AssumeRole' # このロールを引き受ける事を。
      ManagedPolicyArns: #[必須項目ではない] ロールにアタッチするIAM管理ポリシーのARN。
        - 'arn:aws:iam::aws:policy/service-role/AWS_ConfigRole' # 「'arn:aws:iam::aws:policy/service-role/AWSConfigRole'」から変更
      Policies: #[必須項目ではない] 同上
        - PolicyName: root #[必須] 同上
          PolicyDocument: #[必須]
            Version: 2012-10-17
            Statement:
              - Effect: Allow # 許可する。
                Action: 's3:GetBucketAcl' # aclサブリソースを使用して、バケットのアクセス制御リスト(ACL)を返す。
                Resource: !Join # 以下の文字列を結合
                  - '' # 要素間に何も挟まずに
                  - - 'arn:aws:s3:::'
                    - !Ref ConfigBucket # ↑のS3バケットに。
              - Effect: Allow # 許可する。
                Action: 's3:PutObject' # S3バケットにオブジェクトをアップ。
                Resource: !Join # 同上
                  - '' # 同上
                  - - 'arn:aws:s3:::'
                    - !Ref ConfigBucket # ↑のS3バケットの
                    - /AWSLogs/ #←というフォルダ(厳密にいうと階層構造ではなくプレフィックス)
                    - !Ref 'AWS::AccountId' # スタック作成者のアカウントIDを参照する擬似パラメータ
                    - /* #以下の全てのフォルダやオブジェクト
                Condition: # 条件
                  StringEquals: # 以下の文字列と一致する場合
                    's3:x-amz-acl': bucket-owner-full-control
              - Effect: Allow # 許可する。
                Action: 'config:Put*' # configへのプット。
                Resource: '*' # 全て
# 出力
Outputs:
  ConfigRuleForVolumeTagsArn:
    Value: !GetAtt 
      - ConfigRuleForVolumeTags
      - Arn # arn:aws:config:us-east-1:123456789012:config-rule/config-rule-xxxxxx などの AWS ConfigルールのARN。

  ConfigRuleForVolumeTagsConfigRuleId:
    Value: !GetAtt 
      - ConfigRuleForVolumeTags
      - ConfigRuleId # config-rule-xxxxxxなどのAWS ConfigルールのID 

  ConfigRuleForVolumeAutoEnableIOComplianceType:
    Value: !GetAtt 
      - ConfigRuleForVolumeAutoEnableIO
      - Compliance.Type # COMPLIANTやNON_COMPLIANTなどの AWS Configルールのコンプライアンスステータス。 COMPLIANT | NON_COMPLIANT | NOT_APPLICABLE | INSUFFICIENT_DATA などが返ってくる。


パラメーター入力画面へ。

自分で作成して利用する場合は問題ないと思いますが、今回のようにテンプレート作成者以外がスタック作成者になる前提の場合に、

・Description(説明)
・AllowedPattern(正規表現)
・ConstraintDescription(意図外の入力があった際の表示エラー文)
等指定がないので、どういう意図で文字列を入力すべきか一見して不明だな..と感じました。


デフォルト値は設定してあるのでこういうものでしょうか。

今回はそのままデフォルト値のまま作成に進みます。

DeliveryChannelでエラー発生。(同じ方以外は読み飛ばしてください。)

「配信チャンネルの最大数に達したため、
配信チャンネル'AWS-Config-delivery-channel-and-rules-DeliveryChannel-YHJRFW3SR2V3'を置くことに失敗しました。1に達しています。
(サービス AmazonConfig; Status Code: 400; Error Code: MaxNumberOfDeliveryChannelsExceededException;
リクエストID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx; Proxy: null)」

との事です。

こちらに原因とCLIによる解決方法がまとまっていました。
https://dev.classmethod.jp/articles/delete-awsconfig/


完了しました。
コンソール画面も初期状態に戻っています。



完了しました..。が今度はこちら。

※この章のエラーは↑のコードブロックでは書き換えてあります。

ポリシー arn:aws:iam::aws:policy/service-role/AWSConfigRole が存在しないか、アタッチできない。(サービス AmazonIdentityManagement; Status Code: 404; Error Code: NoSuchEntity; リクエストID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx; Proxy: null)

答えはこちらに書いてありました。

2022 年 7 月 5 日に、AWS 管理ポリシー AWSConfigRole は非推奨となります。このポリシーは、よりスコープダウンされたポリシー AWS_ConfigRole に置き換えられます。

↑繰り返しになりますがコードブロック内は投稿前に修正しているので問題なかったかと思います。

再度ひっかかります。

今度はLambda関数です。

リソースハンドラからメッセージが返されました。"nodejsのランタイムパラメータは、AWS Lambdaファンクションの作成または更新にサポートされなくなりました。関数を作成または更新する場合は、新しいランタイム(nodejs16.x)を使用することをお勧めします。(サービス Lambda, Status Code: 400, リクエストID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)" (RequestToken: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx, HandlerErrorCode: InvalidRequest)

「runtimeが古いよ」と怒られてしまいました。(こちらも冒頭のコードブロックでは修正済)

nodejs→nodejs16.xに修正しました。

スタック作成完了です。

出来たリソースを確認してみました。

Configルールは2つはそれぞれ既に評価されていた様子。

1つは準拠、もう1つは非準拠でした。
ここは私がパラメータでデフォルト値の"false"を選択しているのでAutoEnableIOがOFFになっています。

Lambdaが走って準拠するように変更するのでは?と思い暫く待ってみましたが、非準拠のまま変わる様子がありません。

試しに視界に入った「修復」ボタンを押下してみます。

Actionしたようですが、やはり準拠になる様子がないので、まず非準拠を示しているルールについての設定を再確認してみます。

	Owner: CUSTOM_LAMBDA
        SourceDetails:  
          - EventSource: aws.config 
            MessageType: ConfigurationItemChangeNotification

としていましたので「ConfigurationItemChangeNotification」を調べると、

リソースが作成または削除されるか、設定が変更されました。
このメッセージにはこの変更に伴ってAWS Configで作成される設定項目の詳細と変更の種類が含まれます。

とあります。

要は作成されたり変更されたりといったイベントの際に発動する。
という事ですね。

この項目だけでは疑問の答えに直結しなさそうです。

Cloudwathロググループではスタック作成と同タイミングでLambdaを一度実行していそうな履歴がありました。

試しに対象のEBSボリュームを手動で「自動有効化IO」をしてみました。

Configルールに戻って、手動で「再評価」をしてみます。

ルール自体は「準拠」に変更になりました。

再度Lambdaのロググループを確認します。

Lambdaのコードを見てみると注意やインフォーメーションらしきマークが出ていたので

compliance is not defined please fix or add /global compliance/
Read-only global 'compliance' should not be modified
compliance is assigned to but never used
等々

指示通りに書き直したり思考錯誤してみましたが、特に変化はありませんでした。

もう一度Configルールを眺めていると、

そもそも修復アクションは設定されていない事に気付きます。

それを念頭にLambdaのコードを再度眺めているとどうも修復アクションを定義したものではなく、評価をする為のコードのように見えてきましたが、

冒頭のテンプレの説明文では

自動I/Oを有効にする AWSConfigルールを作成します。

としていますので、恐らくこれもそう思いたい一時的なバイアスのような気もします。

手動で修復アクションを追加してみる事も試みましたが、以下のようにAutoEnableIOに関するアクションは見当たりませんでしたので、このあたりは再度勉強をし直して出直す事にしました。

今回は以上になります。

テンプレ内で更新されていなかった部分の修正作業があった事も含めて勉強になる事が幾つかありました。

技術ブログというスタンスではないものの、知識不足もあり理解仕切れていない部分がある点お許しください。

何らかの有益な情報があれば幸いです。
お付き合いいただき有難うございました!

Discussion