AWS Config ルールの AWS Lambda 関数の例 (Node.js) - AWS Config

AWS Config ルールの AWS Lambda 関数の例 (Node.js)

AWS Lambda は、AWS のサービスから発行されたイベントに応じて関数を実行します。カスタム Config ルールの関数は AWS Config から発行されたイベントに応じ、イベントから受け取ったデータと AWS Config API から取得したデータを使用してルールのコンプライアンスを評価します。Config ルールの関数のオペレーションは、設定変更でトリガーされる評価を実行するか、定期的にトリガーされる評価を実行するかで異なります。

AWS Lambda 関数内の一般的なパターンについては、『AWS Lambda Developer Guide』の「プログラミングモデル」を参照してください。

設定変更でトリガーされる評価の関数の例

AWS Config は、カスタムルールのスコープ内にあるリソースの設定変更を検出すると、次の例のような関数を呼び出します。

この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Configuration changes (設定変更)] を選択します。AWS Config API または AWS CLI でルールを作成する場合は、MessageType 属性を ConfigurationItemChangeNotification および OversizedConfigurationItemChangeNotification に設定します。これらの設定により、リソースの変更に伴って AWS Config で設定項目またはサイズが大きすぎる設定項目が生成されるたびに、ルールがトリガーされます。

この例では、リソースを評価し、インスタンスがリソースタイプ AWS::EC2::Instance と一致するかどうかを確認します。AWS Config で設定項目またはサイズが大きすぎる設定項目が生成されると、ルールがトリガーされます。

'use strict'; const aws = require('aws-sdk'); const config = new aws.ConfigService(); // 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. function getConfiguration(resourceType, resourceId, configurationCaptureTime, callback) { config.getResourceConfigHistory({ resourceType, resourceId, laterTime: new Date(configurationCaptureTime), limit: 1 }, (err, data) => { if (err) { callback(err, null); } const configurationItem = data.configurationItems[0]; callback(null, configurationItem); }); } // 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. function getConfigurationItem(invokingEvent, callback) { checkDefined(invokingEvent, 'invokingEvent'); if (isOverSizedChangeNotification(invokingEvent.messageType)) { const configurationItemSummary = checkDefined(invokingEvent.configurationItemSummary, 'configurationItemSummary'); 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. exports.handler = (event, context, callback) => { checkDefined(event, 'event'); const invokingEvent = JSON.parse(event.invokingEvent); const ruleParameters = JSON.parse(event.ruleParameters); getConfigurationItem(invokingEvent, (err, configurationItem) => { if (err) { callback(err); } let compliance = 'NOT_APPLICABLE'; const putEvaluationsRequest = {}; if (isApplicable(configurationItem, event)) { // Invoke the compliance checking function. compliance = evaluateChangeNotificationCompliance(configurationItem, ruleParameters); } // Initializes the request that contains the evaluation results. putEvaluationsRequest.Evaluations = [ { ComplianceResourceType: configurationItem.resourceType, ComplianceResourceId: configurationItem.resourceId, ComplianceType: compliance, OrderingTimestamp: configurationItem.configurationItemCaptureTime, }, ]; putEvaluationsRequest.ResultToken = event.resultToken; // Sends the evaluation results to AWS Config. config.putEvaluations(putEvaluationsRequest, (error, data) => { if (error) { callback(error, null); } else if (data.FailedEvaluations.length > 0) { // Ends the function if evaluation results are not successfully reported to AWS Config. callback(JSON.stringify(data), null); } else { callback(null, data); } }); }); };

関数のオペレーション

関数はランタイムに以下のオペレーションを実行します。

  1. 関数は、AWS Lambda から event オブジェクトが handler 関数に渡されると、実行されます。AWS Lambda からは context オブジェクトも渡されます。関数では、このオブジェクトに含まれている情報とメソッドを実行時に使用できます。この例では、関数はオプションの callback パラメータを受け取り、それを使用して呼び出し元に情報を返しています。

  2. 関数は、イベントの messageType が設定項目であるかサイズが大きすぎる設定項目であるかを確認し、その設定項目を返します。

  3. ハンドラーは、isApplicable 関数を呼び出してリソースが削除されたかどうかを確認します。

  4. ハンドラは evaluateChangeNotificationCompliance 関数を呼び出し、イベントで AWS Config から発行された configurationItem オブジェクトと ruleParameters オブジェクトを渡します。

    関数は最初にリソースが EC2 インスタンスであるかどうかを評価します。リソースが EC2 インスタンスではない場合、関数はコンプライアンス値として NOT_APPLICABLE を返します。

    次に、関数は設定項目の instanceType 属性が desiredInstanceType パラメータ値と等しいかどうかを評価します。値が等しい場合、関数は COMPLIANT を返します。値が等しくない場合、関数は NON_COMPLIANT を返します。

  5. ハンドラは、putEvaluationsRequest オブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトに含まれている Evaluations パラメータは、評価対象のリソースのコンプライアンス結果、リソースタイプ、および ID を識別します。putEvaluationsRequest オブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。

  6. ハンドラは、config クライアントの putEvaluations メソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。

定期的な評価の関数の例

AWS Config は定期的な評価に応じて次の例のような関数を呼び出します。定期的な評価は、AWS Config でのルールの定義時に指定した間隔で発生します。

この例のような関数に関連付けるルールを AWS Config コンソールで作成する場合は、トリガータイプとして [Periodic (定期的)] を選択します。AWS Config API または AWS CLI でルールを作成する場合は、MessageType 属性を ScheduledNotification に設定します。

この例では、指定したリソースの合計数が指定した最大値を超えているかどうかを確認します。

var aws = require('aws-sdk'), // Loads the AWS SDK for JavaScript. config = new aws.ConfigService(), // Constructs a service object to use the aws.ConfigService class. COMPLIANCE_STATES = { COMPLIANT : 'COMPLIANT', NON_COMPLIANT : 'NON_COMPLIANT', NOT_APPLICABLE : 'NOT_APPLICABLE' }; // Receives the event and context from AWS Lambda. exports.handler = function(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), noOfResources = 0; if (isScheduledNotification(invokingEvent)) { countResourceTypes(ruleParameters.applicableResourceType, "", noOfResources, function(err, count) { if (err === null) { var putEvaluationsRequest; // Initializes the request that contains the evaluation results. putEvaluationsRequest = { Evaluations : [ { // Applies the evaluation result to the AWS account published in the event. ComplianceResourceType : 'AWS::::Account', ComplianceResourceId : event.accountId, ComplianceType : evaluateCompliance(ruleParameters.maxCount, count), OrderingTimestamp : new Date() } ], ResultToken : event.resultToken }; // Sends the evaluation results to AWS Config. config.putEvaluations(putEvaluationsRequest, function(err, data) { if (err) { callback(err, null); } else { if (data.FailedEvaluations.length > 0) { // Ends the function execution if evaluation results are not successfully reported callback(JSON.stringify(data)); } callback(null, data); } }); } 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 whether the compliance conditions for the rule are violated. function evaluateCompliance(maxCount, actualCount) { if (actualCount > maxCount) { return COMPLIANCE_STATES.NON_COMPLIANT; } else { return COMPLIANCE_STATES.COMPLIANT; } } // Counts the applicable resources that belong to the AWS account. function countResourceTypes(applicableResourceType, nextToken, count, callback) { config.listDiscoveredResources({resourceType : applicableResourceType, nextToken : nextToken}, function(err, data) { if (err) { callback(err, null); } else { count = count + data.resourceIdentifiers.length; if (data.nextToken !== undefined && data.nextToken != null) { countResourceTypes(applicableResourceType, data.nextToken, count, callback); } callback(null, count); } }); return count; }

関数のオペレーション

関数はランタイムに以下のオペレーションを実行します。

  1. 関数は、AWS Lambda から event オブジェクトが handler 関数に渡されると、実行されます。AWS Lambda からは context オブジェクトも渡されます。関数では、このオブジェクトに含まれている情報とメソッドを実行時に使用できます。この例では、関数はオプションの callback パラメータを受け取り、それを使用して呼び出し元に情報を返しています。

  2. 指定したタイプのリソースをカウントするために、ハンドラーは countResourceTypes 関数を呼び出し、イベントから受け取った applicableResourceType パラメータを渡します。countResourceTypes 関数は、listDiscoveredResources クライアントの config メソッドを呼び出します。クライアントは、該当するリソースの ID のリストを返します。関数は、このリストの長さに基づいて該当するリソースの数を判断し、この数をハンドラーに返します。

  3. ハンドラは、putEvaluationsRequest オブジェクトを初期化し、AWS Config に評価結果を送信する準備を整えます。このオブジェクトには、Evaluations パラメータが含まれています。このパラメータは、コンプライアンス結果とイベントで発行された AWS アカウントを識別します。Evaluations パラメータでは、AWS Config でサポートされている任意のリソースタイプに結果を適用できます。putEvaluationsRequest オブジェクトには、AWS Config のルールとイベントを識別する、イベントの結果トークンも含まれています。

  4. putEvaluationsRequest オブジェクト内で、ハンドラーは evaluateCompliance 関数を呼び出します。この関数は、該当するリソースの数が、イベントから提供された maxCount パラメータに割り当てた最大値を超えているかどうかをテストします。リソース数が最大値を超えている場合、関数は NON_COMPLIANT を返します。リソース数が最大値を超えていない場合、関数は COMPLIANT を返します。

  5. ハンドラは、config クライアントの putEvaluations メソッドにオブジェクトを渡すことで、AWS Config に評価結果を送信します。