solution/solution-compliance-audit-for-data-plane/source/function/ecs-timezone/index.js (201 lines of code) (raw):
"use strict";
const RPCClient = require("@alicloud/pop-core").RPCClient;
const _ = require("lodash");
const httpModule = require('https');
// 配置审计地域
// 根据账号归属站点的不同,选择不同地域:
// 1. 中国站:cn-shanghai
// 2. International:ap-southeast-1
const CONFIG_SERVICE_REGION = 'cn-shanghai';
//合规
const COMPLIANCE_TYPE_COMPLIANT = "COMPLIANT";
//不合规
const COMPLIANCE_TYPE_NON_COMPLIANT = "NON_COMPLIANT";
//不适用
const COMPLIANCE_TYPE_NOT_APPLICABLE = "NOT_APPLICABLE";
const keepAliveAgent = new httpModule.Agent({
keepAlive: false,
});
const requestOption = {
method: 'POST',
formatParams: false,
timeout: 10000,
agent: keepAliveAgent,
};
exports.handler = (event, context, callback) => {
const params = JSON.parse(event.toString());
console.log(params);
console.log(context);
main(params, context)
.then(() => {
callback(null);
})
.catch((err) => callback(err));
};
async function main(eventParams, context) {
const { logger } = context;
const {
invokingEvent: {
configurationItem
},
ruleParameters: {
tagScopes,
timezone
}
} = eventParams;
if (!configurationItem) {
logger.error(`There is no configurationItem in invokingEvent. Params is ${JSON.stringify(eventParams)}`);
return;
}
const { regionId, resourceId, tags, accountId } = configurationItem;
logger.info(`Start evaluating for resource ${resourceId} of account ${accountId} in region ${regionId}`);
//校验资源标签是否在要检测的范围内
if (tagScopes) {
const allowedTags = JSON.parse(tagScopes);
if (!tags) {
logger.info(`Resource ${resourceId} don't need to evaluate`);
return;
}
const resourceTags = JSON.parse(tags);
var needEvaluate = false;
for (let i = 0; i < allowedTags.length; i++) {
if (resourceTags[allowedTags[i].TagKey] != null && resourceTags[allowedTags[i].TagKey].indexOf(allowedTags[i].TagValue) > -1) {
needEvaluate = true;
break;
}
}
//忽略资源不在需要巡检的范围内的资源
if (needEvaluate === false) {
logger.info(`Resource ${resourceId} don't need to evaluate`);
return;
}
}
//构造ECS云助手Client
const client = await getEcsClient(eventParams, context);
//构造请求参数
const params = {
RegionId: regionId,
Type: "RunShellScript",
CommandContent: btoa("timedatectl | grep Time | awk -F ':' '{print $2}'"),
RepeatMode: "Once",
ContentEncoding: "Base64",
InstanceId: [resourceId],
Timeout: 60,
};
//请求云助手
const result = await client.request("RunCommand", params, requestOption);
const { CommandId: commandId, InvokeId: invokeId } = result;
while (true) {
let invocationResult = await getCommandResult(
commandId,
invokeId,
eventParams,
context,
client
);
if (invocationResult !== null) {
invocationResult = invocationResult.trim();
const isCompliant =
invocationResult === `${timezone.trim()}`;
const annotation = isCompliant ? {} : {
desiredValue: timezone.trim(),
configuration: invocationResult,
};
const complianceType = isCompliant ? COMPLIANCE_TYPE_COMPLIANT : COMPLIANCE_TYPE_NON_COMPLIANT;
await putEvaluationResult(complianceType, eventParams, context, annotation);
break;
}
await sleep(2000);
}
}
async function getCommandResult(commandId, invokeId, eventParams, context, client) {
const { regionId, resourceId } = eventParams.invokingEvent.configurationItem;
const { logger } = context;
const params = {
RegionId: regionId,
InvokeId: invokeId,
InstanceId: resourceId,
CommandId: commandId,
};
const result = await client.request(
"DescribeInvocationResults",
params,
requestOption
);
let invocationResult =
result.Invocation.InvocationResults.InvocationResult[0];
if (
!_.isUndefined(invocationResult.InvocationStatus) &&
_.isEqual(invocationResult.InvocationStatus, "Aborted")
) {
logger.error(`执行失败 错误信息 ${invocationResult.ErrorInfo}`);
return "";
} else if (_.isNil(invocationResult.ExitCode)) {
logger.log("脚本执行中,请等待.......");
return null;
} else {
if (_.isEqual(`${invocationResult.ExitCode}`, "0")) {
logger.log(`命令输出结果 ` + Buffer.from(invocationResult.Output, 'base64'));
} else {
logger.error(
`错误码 ${invocationResult.ErrorCode} 错误信息 ${invocationResult.ErrorInfo}`
);
}
const buff = Buffer.from(invocationResult.Output, 'base64');
return buff.toString('ascii');
}
}
async function getEcsClient(eventParams, context) {
const { regionId, accountId } = eventParams.invokingEvent.configurationItem;
//Assume Role到应用账号
const stsClient = new RPCClient({
accessKeyId: context.credentials.accessKeyId,
accessKeySecret: context.credentials.accessKeySecret,
securityToken: context.credentials.securityToken,
endpoint: `https://sts.${regionId}.aliyuncs.com`,
apiVersion: "2015-04-01",
});
const params = {
RegionId: regionId,
RoleArn: `acs:ram::${accountId}:role/${eventParams.ruleParameters.configFcExecutionRoleName}`,
RoleSessionName: "EcsTimezoneInspection",
};
const applicationAccountCredentials = await stsClient.request(
"AssumeRole",
params,
requestOption
);
// 构造 ecs 服务的 client
const ecsClient = new RPCClient({
accessKeyId: applicationAccountCredentials.Credentials.AccessKeyId,
accessKeySecret: applicationAccountCredentials.Credentials.AccessKeySecret,
securityToken: applicationAccountCredentials.Credentials.SecurityToken,
endpoint: `https://ecs.${regionId}.aliyuncs.com`,
apiVersion: "2014-05-26",
});
return ecsClient;
}
async function putEvaluationResult(complianceType, eventParams, context, annotation) {
const {
invokingEvent: {
accountId,
configurationItem: { regionId, resourceId, resourceType },
},
resultToken,
orderingTimestamp,
} = eventParams;
const client = new RPCClient({
accessKeyId: context.credentials.accessKeyId,
accessKeySecret: context.credentials.accessKeySecret,
securityToken: context.credentials.securityToken,
endpoint: `https://config.${CONFIG_SERVICE_REGION}.aliyuncs.com`,
apiVersion: "2019-01-08",
});
const params = {
ResultToken: resultToken,
Evaluations: JSON.stringify([
{
accountId,
annotation: JSON.stringify(annotation),
complianceResourceId: resourceId,
complianceResourceType: resourceType,
complianceRegionId: regionId,
complianceType,
orderingTimestamp,
},
]),
//启用删除模式
DeleteMode: true
};
return client.request("PutEvaluations", params, requestOption);
}
function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}