AWS Configとは
AWS Config は、AWS リソースの設定を評価、監査、審査できるサービスです。Config では、AWS リソースの設定が継続的にモニタリングおよび記録され、望まれる設定に対する記録された設定の評価を自動的に実行できます。Config を使用すると、AWS リソース間の設定や関連性の変更を確認し、詳細なリソース設定履歴を調べ、社内ガイドラインで指定された設定に対する全体的なコンプライアンスを確認できます。これにより、コンプライアンス監査、セキュリティ分析、変更管理、運用上のトラブルシューティングを簡素化できます。
Configルールを使用することで、指定の設定に準拠しているかどうかを評価できます。
Configルールでタグ非準拠を修正する
Configルールに修復アクションを使用することもできますが、SSMドキュメントの書き方に慣れ親しんでいないので、Lambdaで日次で非準拠リソースを取得して修正する形にします!
Configルールの作成
まずはConfigルールを作成します。
今回はOwnerタグをつけていないVPCを評価するルールを作成します。
AWSマネージドルールのrequired-tagsを選択します。
適当な名前を付け、トリガーのリソースにVPCを設定します。
パラメータにはタグキーに「Owner」があるかどうかを評価したいので、tag1KeyにOwnerを設定します。



Lambdaの作成
マネジメントコンソールからLambdaを開き、関数を作成します。
| 設定項目 | 値 | 
|---|---|
| 関数名 | なんでもいいです | 
| ランタイム | Python3.9 | 
| IAMロール | 下記のポリシーが付与されたものをつけてあげてください | 
IAMロールに必要なポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cloudtrail:LookupEvents",
                "config:GetComplianceDetailsByConfigRule",
                "config:GetResourceConfigHistory",
                "tag:TagResources"
            ],
            "Resource": "*"
        }
    ]
}
関数が作成出来たら下記のコードに置き換えて、保存します。
Configルール名は変更してください。
細かくは解説しませんが、非準拠リソースの一覧を取ってきて、そのリソースのイベントの中で最も古いイベントを行った人のユーザー名をOwnerタグの値に設定しています。
したがって、90日以上前に作成されたリソースに関してはタグ付けできません。
(証跡からAthena使ってクエリしたらできると思うので、やりたい方いたら!)
また、副次的にできてしまうリソース、例えば、TGWのアタッチメントを作成した際にできるENIやCloudFormationで作成されたリソースについては、ユーザーがサービス名になるので値もそのCFNを作成したユーザーを入れてくれはしないので、注意が必要です。
import boto3
def get_create_user(resource):
    client = boto3.client('cloudtrail')
    events = []
    response = client.lookup_events(
        LookupAttributes=[
            {
                'AttributeKey': 'ResourceName',
                'AttributeValue': resource
            },
        ]
    )
    events.extend(response['Events'])
    while 'NextToken' in response:
        response = client.lookup_events(
            LookupAttributes=[
                {
                    'AttributeKey': 'ResourceName',
                    'AttributeValue': resource
                },
            ],
            NextToken = response['NextToken']
        )
        events.extend(response['Events'])
    sorted_events = sorted(events, key = lambda x: x['EventTime'])
    oldest_event = sorted_events[0]
    return oldest_event['Username']
    
def set_owner_tag(resource, user):
    client = boto3.client('resourcegroupstaggingapi')
    tags = {
        'Owner' : user
    }
    print(tags)
    try:
        response = client.tag_resources(ResourceARNList = [resource], Tags = tags)
        print('Success {}'.format(resource))
        return True
    except Exception as e:
        raise e
        
def list_non_compliant_resources(rule_name):
    client = boto3.client('config')
    results = []
    response = client.get_compliance_details_by_config_rule(
        ConfigRuleName=rule_name,
        ComplianceTypes=[
            'NON_COMPLIANT'
        ]
    )
    results.extend(response['EvaluationResults'])
    while 'NextToken' in response:
        response = boto3.get_compliance_details_by_config_rule(
            ConfigRuleName=rule_name,
            ComplianceTypes=[
                'NON_COMPLIANT'
            ],
            NextToken = response['NextToken']
        )
        results.extend(response['EvaluationResults'])
    return results
    
def get_resource_arn(resource_id, resource_type):
    client = session.client('config')
    response = client.get_resource_config_history(
        resourceType=resource_type,
        resourceId=resource_id
    )
    return response['configurationItems'][0]['arn']
    
def lambda_handler(event, context):
    results = list_non_compliant_resources('Configルール名')
    for result in results:
        try:
            resource_type = result['EvaluationResultIdentifier']['EvaluationResultQualifier']['ResourceType']
            resource_id = result['EvaluationResultIdentifier']['EvaluationResultQualifier']['ResourceId']
            if not resource_id.startswith('arn'):
                arn = get_resource_arn(resource_id, resource_type)
            else:
                arn = resource_id
            owner = get_create_user(resource_id)
            set_owner_tag(arn, owner)
        except Exception as e:
            print(e)
ここまで出来たら、手動で実行してもよし、EventBridgeで定期的に実行してもよし!です!
まとめ
今回は、検証環境でありがちなOwnerタグのつけ忘れを防止するために、いろいろやってみました!
個人的にはかなり欲しいものだったので、もっとこういうやり方あるよ!って方いたら教えてください!!
少しでもこれで緩和されたらいいな…
読んでいただき、ありがとうございました!


