AWS Config カスタム Lambda ルールの作成
カスタムルールを作成し、AWS Lambda 関数を使用して AWS Config に追加できます。
各カスタムルールは Lambda 関数と関連付けます。この関数には、AWS リソースがルールに準拠しているかどうかを評価するロジックが含まれています。この関数をルールに関連付け、設定変更に応じて、または定期的にルールから関数を呼び出します。次に、関数はリソースがルールに準拠しているかどうかを評価し、評価結果を AWS Config に送信します。
AWS ルール開発キット (RDK) は、直感的で生産的な「Compliance-as-Code」ワークフローをサポートするように設計されています。カスタム Lambda 関数にサポートされる AWS Config ルールの展開に関連する、差別化できない重労働の多くを抽象化し、効率的な開発・展開・監視の反復プロセスを提供します。
詳細な手順については、「AWS ルール開発キット (RDK) ドキュメント
AWS Lambda は、AWS のサービスから発行されたイベントに応答して関数を実行します。AWS Config カスタム Lambda ルールの関数は AWS Config から発行されたイベントを受け取り、イベントから受け取ったデータと AWS Config API から取得したデータを使用してルールのコンプライアンスを評価します。Config ルールの関数のオペレーションは、設定変更にトリガーされて評価するか、定期的にトリガーされて評価するかによって異なります。
AWS Lambda 関数内の一般的なパターンについては、「AWS Lambda デベロッパーガイド」の「プログラミングモデル」を参照してください。
- Example Function for Evaluations Triggered by Configuration Changes
-
AWS Config は、カスタムルールのスコープ内にあるリソースの設定変更を検出すると、次の例のような関数を呼び出します。
この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Configuration changes] (設定変更) を選択します。AWS Config API または AWS CLI でルールを作成する場合は、
MessageType属性をConfigurationItemChangeNotificationおよびOversizedConfigurationItemChangeNotificationに設定します。これらの設定により、リソースの変更に伴って AWS Config で設定項目またはサイズが大きすぎる設定項目が生成されるたびに、ルールがトリガーされます。この例では、リソースを評価し、インスタンスがリソースタイプ
AWS::EC2::Instanceと一致するかどうかを確認します。AWS Config で設定項目またはサイズが大きすぎる設定項目が生成されると、ルールがトリガーされます。'use strict'; import { ConfigServiceClient, GetResourceConfigHistoryCommand, PutEvaluationsCommand } from "@aws-sdk/client-config-service"; const configClient = new ConfigServiceClient({}); // Helper function used to validate input function checkDefined(reference, referenceName) { if (!reference) { throw new Error(`Error: ${referenceName} is not defined`); } return reference; } // Check whether the message type is OversizedConfigurationItemChangeNotification, function isOverSizedChangeNotification(messageType) { checkDefined(messageType, 'messageType'); return messageType === 'OversizedConfigurationItemChangeNotification'; } // Get the configurationItem for the resource using the getResourceConfigHistory API. async function getConfiguration(resourceType, resourceId, configurationCaptureTime, callback) { const input = { resourceType, resourceId, laterTime: new Date(configurationCaptureTime), limit: 1 }; const command = new GetResourceConfigHistoryCommand(input); await configClient.send(command).then( (data) => { callback(null, data.configurationItems[0]); }, (error) => { callback(error, null); } ); } // Convert the oversized configuration item from the API model to the original invocation model. function convertApiConfiguration(apiConfiguration) { apiConfiguration.awsAccountId = apiConfiguration.accountId; apiConfiguration.ARN = apiConfiguration.arn; apiConfiguration.configurationStateMd5Hash = apiConfiguration.configurationItemMD5Hash; apiConfiguration.configurationItemVersion = apiConfiguration.version; apiConfiguration.configuration = JSON.parse(apiConfiguration.configuration); if ({}.hasOwnProperty.call(apiConfiguration, 'relationships')) { for (let i = 0; i < apiConfiguration.relationships.length; i++) { apiConfiguration.relationships[i].name = apiConfiguration.relationships[i].relationshipName; } } return apiConfiguration; } // Based on the message type, get the configuration item either from the configurationItem object in the invoking event or with the getResourceConfigHistory API in the getConfiguration function. async function getConfigurationItem(invokingEvent, callback) { checkDefined(invokingEvent, 'invokingEvent'); if (isOverSizedChangeNotification(invokingEvent.messageType)) { const configurationItemSummary = checkDefined(invokingEvent.configurationItemSummary, 'configurationItemSummary'); await getConfiguration(configurationItemSummary.resourceType, configurationItemSummary.resourceId, configurationItemSummary.configurationItemCaptureTime, (err, apiConfigurationItem) => { if (err) { callback(err); } const configurationItem = convertApiConfiguration(apiConfigurationItem); callback(null, configurationItem); }); } else { checkDefined(invokingEvent.configurationItem, 'configurationItem'); callback(null, invokingEvent.configurationItem); } } // Check whether the resource has been deleted. If the resource was deleted, then the evaluation returns not applicable. function isApplicable(configurationItem, event) { checkDefined(configurationItem, 'configurationItem'); checkDefined(event, 'event'); const status = configurationItem.configurationItemStatus; const eventLeftScope = event.eventLeftScope; return (status === 'OK' || status === 'ResourceDiscovered') && eventLeftScope === false; } // In this example, the resource is compliant if it is an instance and its type matches the type specified as the desired type. // If the resource is not an instance, then this resource is not applicable. function evaluateChangeNotificationCompliance(configurationItem, ruleParameters) { checkDefined(configurationItem, 'configurationItem'); checkDefined(configurationItem.configuration, 'configurationItem.configuration'); checkDefined(ruleParameters, 'ruleParameters'); if (configurationItem.resourceType !== 'AWS::EC2::Instance') { return 'NOT_APPLICABLE'; } else if (ruleParameters.desiredInstanceType === configurationItem.configuration.instanceType) { return 'COMPLIANT'; } return 'NON_COMPLIANT'; } // Receives the event and context from AWS Lambda. export const handler = async (event, context) => { checkDefined(event, 'event'); const invokingEvent = JSON.parse(event.invokingEvent); const ruleParameters = JSON.parse(event.ruleParameters); await getConfigurationItem(invokingEvent, async (err, configurationItem) => { let compliance = 'NOT_APPLICABLE'; let annotation = ''; const putEvaluationsRequest = {}; if (isApplicable(configurationItem, event)) { // Invoke the compliance checking function. compliance = evaluateChangeNotificationCompliance(configurationItem, ruleParameters); if (compliance === "NON_COMPLIANT") { annotation = "This is an annotation describing why the resource is not compliant."; } } // Initializes the request that contains the evaluation results. if (annotation) { putEvaluationsRequest.Evaluations = [ { ComplianceResourceType: configurationItem.resourceType, ComplianceResourceId: configurationItem.resourceId, ComplianceType: compliance, OrderingTimestamp: new Date(configurationItem.configurationItemCaptureTime), Annotation: annotation }, ]; } else { putEvaluationsRequest.Evaluations = [ { ComplianceResourceType: configurationItem.resourceType, ComplianceResourceId: configurationItem.resourceId, ComplianceType: compliance, OrderingTimestamp: new Date(configurationItem.configurationItemCaptureTime), }, ]; } putEvaluationsRequest.ResultToken = event.resultToken; // Sends the evaluation results to AWS Config. await configClient.send(new PutEvaluationsCommand(putEvaluationsRequest)); }); };関数のオペレーション
関数はランタイムに以下のオペレーションを実行します。
-
関数は、AWS Lambda から
eventオブジェクトがhandler関数に渡されると、実行されます。この例では、関数がオプションのcallbackパラメータを承諾し、それを使用して発信者に情報を返します。AWS Lambda はcontextオブジェクトも渡します。このオブジェクトには関数の実行中に使用できる情報とメソッドが含まれています。Lambda の新しいバージョンでは、コンテキストは使用されなくなりました。 -
関数は、イベントの
messageTypeが設定項目であるかサイズが大きすぎる設定項目であるかを確認し、その設定項目を返します。 -
ハンドラーは、
isApplicable関数を呼び出してリソースが削除されたかどうかを確認します。注記
削除されたリソースをレポートするルールは、不必要なルール評価を避けるために、
NOT_APPLICABLEの評価結果を返す必要があります。 -
ハンドラーは
evaluateChangeNotificationCompliance関数を呼び出し、イベントで AWS Config から発行されたconfigurationItemオブジェクトとruleParametersオブジェクトを渡します。関数は最初にリソースが EC2 インスタンスであるかどうかを評価します。リソースが EC2 インスタンスではない場合、関数はコンプライアンス値として
NOT_APPLICABLEを返します。次に、関数は設定項目の
instanceType属性がdesiredInstanceTypeパラメータ値と等しいかどうかを評価します。値が等しい場合、関数はCOMPLIANTを返します。値が等しくない場合、関数はNON_COMPLIANTを返します。 -
ハンドラーは、
putEvaluationsRequestオブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトに含まれているEvaluationsパラメータは、評価対象のリソースのコンプライアンス結果、リソースタイプ、および ID を識別します。putEvaluationsRequestオブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。 -
ハンドラーは、
configクライアントのputEvaluationsメソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。
-
- Example Function for Periodic Evaluations
-
AWS Config は定期的な評価に応じて次の例のような関数を呼び出します。定期的な評価は、AWS Config でのルールの定義時に指定した間隔で発生します。
この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Periodic] (定期的) を選択します。AWS Config API または AWS CLI でルールを作成する場合は、
MessageType属性をScheduledNotificationに設定します。この例では、指定したリソースの合計数が指定した最大値を超えているかどうかを確認します。
'use strict'; import { ConfigServiceClient, ListDiscoveredResourcesCommand, PutEvaluationsCommand } from "@aws-sdk/client-config-service"; const configClient = new ConfigServiceClient({}); // Receives the event and context from AWS Lambda. export const handler = async (event, context, callback) => { // Parses the invokingEvent and ruleParameters values, which contain JSON objects passed as strings. var invokingEvent = JSON.parse(event.invokingEvent), ruleParameters = JSON.parse(event.ruleParameters), numberOfResources = 0; if (isScheduledNotification(invokingEvent) && hasValidRuleParameters(ruleParameters, callback)) { await countResourceTypes(ruleParameters.applicableResourceType, "", numberOfResources, async function (err, count) { if (err === null) { var putEvaluationsRequest; const compliance = evaluateCompliance(ruleParameters.maxCount, count); var annotation = ''; if (compliance === "NON_COMPLIANT") { annotation = "Description of why the resource is not compliant."; } // Initializes the request that contains the evaluation results. if (annotation) { putEvaluationsRequest = { Evaluations: [{ // Applies the evaluation result to the AWS account published in the event. ComplianceResourceType: 'AWS::::Account', ComplianceResourceId: event.accountId, ComplianceType: compliance, OrderingTimestamp: new Date(), Annotation: annotation }], ResultToken: event.resultToken }; } else { putEvaluationsRequest = { Evaluations: [{ // Applies the evaluation result to the AWS account published in the event. ComplianceResourceType: 'AWS::::Account', ComplianceResourceId: event.accountId, ComplianceType: compliance, OrderingTimestamp: new Date() }], ResultToken: event.resultToken }; } // Sends the evaluation results to AWS Config. try { await configClient.send(new PutEvaluationsCommand(putEvaluationsRequest)); } catch (e) { callback(e, null); } } else { callback(err, null); } }); } else { console.log("Invoked for a notification other than Scheduled Notification... Ignoring."); } }; // Checks whether the invoking event is ScheduledNotification. function isScheduledNotification(invokingEvent) { return (invokingEvent.messageType === 'ScheduledNotification'); } // Checks the rule parameters to see if they are valid function hasValidRuleParameters(ruleParameters, callback) { // Regular express to verify that applicable resource given is a resource type const awsResourcePattern = /^AWS::(\w*)::(\w*)$/; const isApplicableResourceType = awsResourcePattern.test(ruleParameters.applicableResourceType); // Check to make sure the maxCount in the parameters is an integer const maxCountIsInt = !isNaN(ruleParameters.maxCount) && parseInt(Number(ruleParameters.maxCount)) == ruleParameters.maxCount && !isNaN(parseInt(ruleParameters.maxCount, 10)); if (!isApplicableResourceType) { callback("The applicableResourceType parameter is not a valid resource type.", null); } if (!maxCountIsInt) { callback("The maxCount parameter is not a valid integer.", null); } return isApplicableResourceType && maxCountIsInt; } // Checks whether the compliance conditions for the rule are violated. function evaluateCompliance(maxCount, actualCount) { if (actualCount > maxCount) { return "NON_COMPLIANT"; } else { return "COMPLIANT"; } } // Counts the applicable resources that belong to the AWS account. async function countResourceTypes(applicableResourceType, nextToken, count, callback) { const input = { resourceType: applicableResourceType, nextToken: nextToken }; const command = new ListDiscoveredResourcesCommand(input); try { const response = await configClient.send(command); count = count + response.resourceIdentifiers.length; if (response.nextToken !== undefined && response.nextToken != null) { countResourceTypes(applicableResourceType, response.nextToken, count, callback); } callback(null, count); } catch (e) { callback(e, null); } return count; }関数のオペレーション
関数はランタイムに以下のオペレーションを実行します。
-
関数は、AWS Lambda から
eventオブジェクトがhandler関数に渡されると、実行されます。この例では、関数がオプションのcallbackパラメータを承諾し、それを使用して発信者に情報を返します。AWS Lambda はcontextオブジェクトも渡します。このオブジェクトには関数の実行中に使用できる情報とメソッドが含まれています。Lambda の新しいバージョンでは、コンテキストは使用されなくなりました。 -
指定したタイプのリソースをカウントする場合、ハンドラーは
countResourceTypes関数を呼び出し、イベントから受け取ったapplicableResourceTypeパラメータを渡します。countResourceTypes関数は、configクライアントのlistDiscoveredResourcesメソッドを呼び出します。クライアントは、該当するリソースの ID のリストを返します。関数は、このリストの長さに基づいて該当するリソースの数を判断し、この数をハンドラーに返します。 -
ハンドラーは、
putEvaluationsRequestオブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトには、Evaluationsパラメータが含まれています。このパラメータは、コンプライアンス結果とイベントで発行された AWS アカウント アカウントを識別します。Evaluationsパラメータでは、AWS Config でサポートされている任意のリソースタイプに結果を適用できます。putEvaluationsRequestオブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。 -
putEvaluationsRequestオブジェクト内で、ハンドラーはevaluateCompliance関数を呼び出します。この関数は、該当するリソースの数が、イベントから提供されたmaxCountパラメータに割り当てた最大値を超えているかどうかをテストします。リソース数が最大値を超えている場合、関数はNON_COMPLIANTを返します。リソース数が最大値を超えていない場合、関数はCOMPLIANTを返します。 -
ハンドラーは、
configクライアントのputEvaluationsメソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。
-
AWS Lambda は、AWS のサービスから発行されたイベントに応答して関数を実行します。AWS Config カスタム Lambda ルールの関数は AWS Config から発行されたイベントを受け取り、イベントから受け取ったデータと AWS Config API から取得したデータを使用してルールのコンプライアンスを評価します。Config ルールの関数のオペレーションは、設定変更にトリガーされて評価するか、定期的にトリガーされて評価するかによって異なります。
AWS Lambda 関数内の一般的なパターンについては、「AWS Lambda デベロッパーガイド」の「プログラミングモデル」を参照してください。
- Example Function for Evaluations Triggered by Configuration Changes
-
AWS Config は、カスタムルールのスコープ内にあるリソースの設定変更を検出すると、次の例のような関数を呼び出します。
この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Configuration changes] (設定変更) を選択します。AWS Config API または AWS CLI でルールを作成する場合は、
MessageType属性をConfigurationItemChangeNotificationおよびOversizedConfigurationItemChangeNotificationに設定します。これらの設定により、リソースの変更に伴って AWS Config で設定項目またはサイズが大きすぎる設定項目が生成されるたびに、ルールがトリガーされます。import botocore import boto3 import json import datetime # Set to True to get the lambda to assume the Role attached on the Config Service (useful for cross-account). ASSUME_ROLE_MODE = False # This gets the client after assuming the Config service role # either in the same AWS account or cross-account. def get_client(service, event): """Return the service boto client. It should be used instead of directly calling the client. Keyword arguments: service -- the service name used for calling the boto.client() event -- the event variable given in the lambda handler """ if not ASSUME_ROLE_MODE: return boto3.client(service) credentials = get_assume_role_credentials(event["executionRoleArn"]) return boto3.client(service, aws_access_key_id=credentials['AccessKeyId'], aws_secret_access_key=credentials['SecretAccessKey'], aws_session_token=credentials['SessionToken'] ) # Helper function used to validate input def check_defined(reference, reference_name): if not reference: raise Exception('Error: ', reference_name, 'is not defined') return reference # Check whether the message is OversizedConfigurationItemChangeNotification or not def is_oversized_changed_notification(message_type): check_defined(message_type, 'messageType') return message_type == 'OversizedConfigurationItemChangeNotification' # Get configurationItem using getResourceConfigHistory API # in case of OversizedConfigurationItemChangeNotification def get_configuration(resource_type, resource_id, configuration_capture_time): result = AWS_CONFIG_CLIENT.get_resource_config_history( resourceType=resource_type, resourceId=resource_id, laterTime=configuration_capture_time, limit=1) configurationItem = result['configurationItems'][0] return convert_api_configuration(configurationItem) # Convert from the API model to the original invocation model def convert_api_configuration(configurationItem): for k, v in configurationItem.items(): if isinstance(v, datetime.datetime): configurationItem[k] = str(v) configurationItem['awsAccountId'] = configurationItem['accountId'] configurationItem['ARN'] = configurationItem['arn'] configurationItem['configurationStateMd5Hash'] = configurationItem['configurationItemMD5Hash'] configurationItem['configurationItemVersion'] = configurationItem['version'] configurationItem['configuration'] = json.loads(configurationItem['configuration']) if 'relationships' in configurationItem: for i in range(len(configurationItem['relationships'])): configurationItem['relationships'][i]['name'] = configurationItem['relationships'][i]['relationshipName'] return configurationItem # Based on the type of message get the configuration item # either from configurationItem in the invoking event # or using the getResourceConfigHistory API in getConfiguration function. def get_configuration_item(invokingEvent): check_defined(invokingEvent, 'invokingEvent') if is_oversized_changed_notification(invokingEvent['messageType']): configurationItemSummary = check_defined(invokingEvent['configurationItemSummary'], 'configurationItemSummary') return get_configuration(configurationItemSummary['resourceType'], configurationItemSummary['resourceId'], configurationItemSummary['configurationItemCaptureTime']) return check_defined(invokingEvent['configurationItem'], 'configurationItem') # Check whether the resource has been deleted. If it has, then the evaluation is unnecessary. def is_applicable(configurationItem, event): try: check_defined(configurationItem, 'configurationItem') check_defined(event, 'event') except: return True status = configurationItem['configurationItemStatus'] eventLeftScope = event['eventLeftScope'] if status == 'ResourceDeleted': print("Resource Deleted, setting Compliance Status to NOT_APPLICABLE.") return (status == 'OK' or status == 'ResourceDiscovered') and not eventLeftScope def get_assume_role_credentials(role_arn): sts_client = boto3.client('sts') try: assume_role_response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="configLambdaExecution") return assume_role_response['Credentials'] except botocore.exceptions.ClientError as ex: # Scrub error message for any internal account info leaks if 'AccessDenied' in ex.response['Error']['Code']: ex.response['Error']['Message'] = "AWS Config does not have permission to assume the IAM role." else: ex.response['Error']['Message'] = "InternalError" ex.response['Error']['Code'] = "InternalError" raise ex def evaluate_change_notification_compliance(configuration_item, rule_parameters): check_defined(configuration_item, 'configuration_item') check_defined(configuration_item['configuration'], 'configuration_item[\'configuration\']') if rule_parameters: check_defined(rule_parameters, 'rule_parameters') if (configuration_item['resourceType'] != 'AWS::EC2::Instance'): return 'NOT_APPLICABLE' elif rule_parameters.get('desiredInstanceType'): if (configuration_item['configuration']['instanceType'] in rule_parameters['desiredInstanceType']): return 'COMPLIANT' return 'NON_COMPLIANT' def lambda_handler(event, context): global AWS_CONFIG_CLIENT check_defined(event, 'event') invoking_event = json.loads(event['invokingEvent']) rule_parameters = {} if 'ruleParameters' in event: rule_parameters = json.loads(event['ruleParameters']) compliance_value = 'NOT_APPLICABLE' AWS_CONFIG_CLIENT = get_client('config', event) configuration_item = get_configuration_item(invoking_event) if is_applicable(configuration_item, event): compliance_value = evaluate_change_notification_compliance( configuration_item, rule_parameters) response = AWS_CONFIG_CLIENT.put_evaluations( Evaluations=[ { 'ComplianceResourceType': invoking_event['configurationItem']['resourceType'], 'ComplianceResourceId': invoking_event['configurationItem']['resourceId'], 'ComplianceType': compliance_value, 'OrderingTimestamp': invoking_event['configurationItem']['configurationItemCaptureTime'] }, ], ResultToken=event['resultToken'])関数のオペレーション
関数はランタイムに以下のオペレーションを実行します。
-
関数は、AWS Lambda から
eventオブジェクトがhandler関数に渡されると、実行されます。この例では、関数がオプションのcallbackパラメータを承諾し、それを使用して発信者に情報を返します。AWS Lambda はcontextオブジェクトも渡します。このオブジェクトには関数の実行中に使用できる情報とメソッドが含まれています。Lambda の新しいバージョンでは、コンテキストは使用されなくなりました。 -
関数は、イベントの
messageTypeが設定項目であるかサイズが大きすぎる設定項目であるかを確認し、その設定項目を返します。 -
ハンドラーは、
isApplicable関数を呼び出してリソースが削除されたかどうかを確認します。注記
削除されたリソースをレポートするルールは、不必要なルール評価を避けるために、
NOT_APPLICABLEの評価結果を返す必要があります。 -
ハンドラーは
evaluateChangeNotificationCompliance関数を呼び出し、イベントでconfigurationItemから発行されたruleParametersオブジェクトと AWS Config オブジェクトを渡します。関数は最初にリソースが EC2 インスタンスであるかどうかを評価します。リソースが EC2 インスタンスではない場合、関数はコンプライアンス値として
NOT_APPLICABLEを返します。次に、関数は設定項目の
instanceType属性がdesiredInstanceTypeパラメータ値と等しいかどうかを評価します。値が等しい場合、関数はCOMPLIANTを返します。値が等しくない場合、関数はNON_COMPLIANTを返します。 -
ハンドラーは、
putEvaluationsRequestオブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトに含まれているEvaluationsパラメータは、評価対象のリソースのコンプライアンス結果、リソースタイプ、および ID を識別します。putEvaluationsRequestオブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。 -
ハンドラーは、
configクライアントのputEvaluationsメソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。
-
- Example Function for Periodic Evaluations
-
AWS Config は定期的な評価に応じて次の例のような関数を呼び出します。定期的な評価は、AWS Config でのルールの定義時に指定した間隔で発生します。
この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Periodic] (定期的) を選択します。AWS Config API または AWS CLI でルールを作成する場合は、
MessageType属性をScheduledNotificationに設定します。import botocore import boto3 import json import datetime # Set to True to get the lambda to assume the Role attached on the Config Service (useful for cross-account). ASSUME_ROLE_MODE = False DEFAULT_RESOURCE_TYPE = 'AWS::::Account' # This gets the client after assuming the Config service role # either in the same AWS account or cross-account. def get_client(service, event): """Return the service boto client. It should be used instead of directly calling the client. Keyword arguments: service -- the service name used for calling the boto.client() event -- the event variable given in the lambda handler """ if not ASSUME_ROLE_MODE: return boto3.client(service) credentials = get_assume_role_credentials(event["executionRoleArn"]) return boto3.client(service, aws_access_key_id=credentials['AccessKeyId'], aws_secret_access_key=credentials['SecretAccessKey'], aws_session_token=credentials['SessionToken'] ) def get_assume_role_credentials(role_arn): sts_client = boto3.client('sts') try: assume_role_response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="configLambdaExecution") return assume_role_response['Credentials'] except botocore.exceptions.ClientError as ex: # Scrub error message for any internal account info leaks if 'AccessDenied' in ex.response['Error']['Code']: ex.response['Error']['Message'] = "AWS Config does not have permission to assume the IAM role." else: ex.response['Error']['Message'] = "InternalError" ex.response['Error']['Code'] = "InternalError" raise ex # Check whether the message is a ScheduledNotification or not. def is_scheduled_notification(message_type): return message_type == 'ScheduledNotification' def count_resource_types(applicable_resource_type, next_token, count): resource_identifier = AWS_CONFIG_CLIENT.list_discovered_resources(resourceType=applicable_resource_type, nextToken=next_token) updated = count + len(resource_identifier['resourceIdentifiers']); return updated # Evaluates the configuration items in the snapshot and returns the compliance value to the handler. def evaluate_compliance(max_count, actual_count): return 'NON_COMPLIANT' if int(actual_count) > int(max_count) else 'COMPLIANT' def evaluate_parameters(rule_parameters): if 'applicableResourceType' not in rule_parameters: raise ValueError('The parameter with "applicableResourceType" as key must be defined.') if not rule_parameters['applicableResourceType']: raise ValueError('The parameter "applicableResourceType" must have a defined value.') return rule_parameters # This generate an evaluation for config def build_evaluation(resource_id, compliance_type, event, resource_type=DEFAULT_RESOURCE_TYPE, annotation=None): """Form an evaluation as a dictionary. Usually suited to report on scheduled rules. Keyword arguments: resource_id -- the unique id of the resource to report compliance_type -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE event -- the event variable given in the lambda handler resource_type -- the CloudFormation resource type (or AWS::::Account) to report on the rule (default DEFAULT_RESOURCE_TYPE) annotation -- an annotation to be added to the evaluation (default None) """ eval_cc = {} if annotation: eval_cc['Annotation'] = annotation eval_cc['ComplianceResourceType'] = resource_type eval_cc['ComplianceResourceId'] = resource_id eval_cc['ComplianceType'] = compliance_type eval_cc['OrderingTimestamp'] = str(json.loads(event['invokingEvent'])['notificationCreationTime']) return eval_cc def lambda_handler(event, context): global AWS_CONFIG_CLIENT evaluations = [] rule_parameters = {} resource_count = 0 max_count = 0 invoking_event = json.loads(event['invokingEvent']) if 'ruleParameters' in event: rule_parameters = json.loads(event['ruleParameters']) valid_rule_parameters = evaluate_parameters(rule_parameters) compliance_value = 'NOT_APPLICABLE' AWS_CONFIG_CLIENT = get_client('config', event) if is_scheduled_notification(invoking_event['messageType']): result_resource_count = count_resource_types(valid_rule_parameters['applicableResourceType'], '', resource_count) if valid_rule_parameters.get('maxCount'): max_count = valid_rule_parameters['maxCount'] compliance_value = evaluate_compliance(max_count, result_resource_count) evaluations.append(build_evaluation(event['accountId'], compliance_value, event, resource_type=DEFAULT_RESOURCE_TYPE)) response = AWS_CONFIG_CLIENT.put_evaluations(Evaluations=evaluations, ResultToken=event['resultToken'])関数のオペレーション
関数はランタイムに以下のオペレーションを実行します。
-
関数は、AWS Lambda から
eventオブジェクトがhandler関数に渡されると、実行されます。この例では、関数がオプションのcallbackパラメータを承諾し、それを使用して発信者に情報を返します。AWS Lambda はcontextオブジェクトも渡します。このオブジェクトには関数の実行中に使用できる情報とメソッドが含まれています。Lambda の新しいバージョンでは、コンテキストは使用されなくなりました。 -
指定したタイプのリソースをカウントする場合、ハンドラーは
countResourceTypes関数を呼び出し、イベントから受け取ったapplicableResourceTypeパラメータを渡します。countResourceTypes関数は、configクライアントのlistDiscoveredResourcesメソッドを呼び出します。クライアントは、該当するリソースの ID のリストを返します。関数は、このリストの長さに基づいて該当するリソースの数を判断し、この数をハンドラーに返します。 -
ハンドラーは、
putEvaluationsRequestオブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトには、Evaluationsパラメータが含まれています。このパラメータは、コンプライアンス結果とイベントで発行された AWS アカウント アカウントを識別します。Evaluationsパラメータでは、AWS Config でサポートされている任意のリソースタイプに結果を適用できます。putEvaluationsRequestオブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。 -
putEvaluationsRequestオブジェクト内で、ハンドラーはevaluateCompliance関数を呼び出します。この関数は、該当するリソースの数が、イベントから提供されたmaxCountパラメータに割り当てた最大値を超えているかどうかをテストします。リソース数が最大値を超えている場合、関数はNON_COMPLIANTを返します。リソース数が最大値を超えていない場合、関数はCOMPLIANTを返します。 -
ハンドラーは、
configクライアントのputEvaluationsメソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。
-
ルールのトリガーが発生すると、AWS Config はイベントを発行してルールの AWS Lambda 関数を呼び出します。次に、AWS Lambda はイベントを関数のハンドラーに渡して関数を実行します。
- Example Event for Evaluations Triggered by Configuration Changes
-
AWS Config は、ルールのスコープ内にあるリソースで設定変更を検出すると、イベントを発行します。次のイベント例は、EC2 インスタンスの設定変更でルールがトリガーされたことを示しています。
{ "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2016-02-17T01:36:34.043Z\",\"awsAccountId\":\"123456789012\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"i-00000000\",\"ARN\":\"arn:aws:ec2:us-east-2:123456789012:instance/i-00000000\",\"awsRegion\":\"us-east-2\",\"availabilityZone\":\"us-east-2a\",\"resourceType\":\"AWS::EC2::Instance\",\"tags\":{\"Foo\":\"Bar\"},\"relationships\":[{\"resourceId\":\"eipalloc-00000000\",\"resourceType\":\"AWS::EC2::EIP\",\"name\":\"Is attached to ElasticIp\"}],\"configuration\":{\"foo\":\"bar\"}},\"messageType\":\"ConfigurationItemChangeNotification\"}", "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", "resultToken": "myResultToken", "eventLeftScope":false, "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", "configRuleArn": "arn:aws:config:us-east-2:123456789012:config-rule/config-rule-0123456", "configRuleName": "change-triggered-config-rule", "configRuleId": "config-rule-0123456", "accountId": "123456789012", "version": "1.0" } - Example Event for Evaluations Triggered by Oversized Configuration Changes
-
一部のリソースの変更では、サイズが大きすぎる設定項目が生成されます。次のイベント例は、EC2 インスタンスのサイズが大きすぎる設定変更でルールがトリガーされたことを示しています。
{ "invokingEvent": "{\"configurationItemSummary\": {\"changeType\": \"UPDATE\",\"configurationItemVersion\": \"1.2\",\"configurationItemCaptureTime\":\"2016-10-06T16:46:16.261Z\",\"configurationStateId\": 0,\"awsAccountId\":\"123456789012\",\"configurationItemStatus\": \"OK\",\"resourceType\": \"AWS::EC2::Instance\",\"resourceId\":\"i-00000000\",\"resourceName\":null,\"ARN\":\"arn:aws:ec2:us-west-2:123456789012:instance/i-00000000\",\"awsRegion\": \"us-west-2\",\"availabilityZone\":\"us-west-2a\",\"configurationStateMd5Hash\":\"8f1ee69b287895a0f8bc5753eca68e96\",\"resourceCreationTime\":\"2016-10-06T16:46:10.489Z\"},\"messageType\":\"OversizedConfigurationItemChangeNotification\"}", "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", "resultToken": "myResultToken", "eventLeftScope":false, "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", "configRuleArn": "arn:aws:config:us-east-2:123456789012:config-rule/config-rule-ec2-managed-instance-inventory", "configRuleName": "change-triggered-config-rule", "configRuleId": "config-rule-0123456", "accountId": "123456789012", "version": "1.0" } - Example Event for Evaluations Triggered by Periodic Frequency
-
AWS Config は、指定された間隔 (24 時間ごとなど) でリソースを評価するときにイベントを発行します。次のイベント例は、定期的な間隔でルールがトリガーされたことを示しています。
{ "invokingEvent": "{\"awsAccountId\":\"123456789012\",\"notificationCreationTime\":\"2016-07-13T21:50:00.373Z\",\"messageType\":\"ScheduledNotification\",\"recordVersion\":\"1.0\"}", "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", "resultToken": "myResultToken", "eventLeftScope":false, "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", "configRuleArn": "arn:aws:config:us-east-2:123456789012:config-rule/config-rule-0123456", "configRuleName": "periodic-config-rule", "configRuleId": "config-rule-6543210", "accountId": "123456789012", "version": "1.0" }
イベントの属性
AWS Config イベントの JSON オブジェクトには、以下の属性が含まれています。
invokingEvent-
ルールの評価をトリガーするイベント。リソースの設定変更に応じてイベントが発行される場合、この属性の値は JSON
configurationItemまたはconfigurationItemSummary(サイズが大きすぎる設定項目の場合) を含む文字列です。設定項目は、AWS Config で変更を検出した時点のリソースの状態を示します。設定項目の例については、「設定履歴の表示」のget-resource-config-historyAWS CLI コマンドで生成される出力を参照してください。定期的な評価のためにイベントが発行される場合、値は JSON オブジェクトを含む文字列です。オブジェクトには、トリガーされた評価に関する情報が含まれています。
それぞれのタイプのイベントで、次の Node.js 例に示すように、関数は JSON パーサーで文字列を解析して文字列の内容を評価する必要があります。
var invokingEvent = JSON.parse(event.invokingEvent); ruleParameters-
評価ロジックの一部として関数が処理するキー/値ペア。AWS Config コンソールを使用してカスタム Lambda ルールを作成するときにパラメータを定義します。
PutConfigRuleAWS Config API リクエストまたはput-config-ruleAWS CLI コマンドのInputParameters属性でも、パラメータを定義できます。パラメータの JSON コードが文字列に含まれているため、次の Node.js 例で示すように、関数は JSON パーサーで文字列を解析して文字列の内容を評価する必要があります。
var ruleParameters = JSON.parse(event.ruleParameters); resultToken-
PutEvaluations呼び出しで関数から AWS Config に渡すトークン。 eventLeftScope-
評価対象の AWS リソースがルールのスコープから削除されているかどうかを示すブール値。値が
trueの場合、関数は、PutEvaluations呼び出しのComplianceType属性の値としてNOT_APPLICABLEを渡すことで、評価を無視できることを示します。 executionRoleArn-
AWS Config に割り当てられる IAM ロールの ARN。
configRuleArn-
AWS Config がルールに割り当てた ARN。
configRuleName-
AWS Config がイベントを発行して関数を呼び出す原因となったルールに割り当てた名前。
configRuleId-
AWS Config がルールに割り当てた ID。
accountId-
ルールを所有する AWS アカウント の ID。
version-
AWS によって割り当てられたバージョン番号。AWS が AWS Config イベントに属性を追加すると、バージョンはインクリメントされます。関数に必要な属性が特定のバージョン以上のイベントにのみある場合、その関数はこの属性の値を確認できます。
AWS Config イベントの現行バージョンは 1.0 です。