diff --git a/cloudformation/AWS-Config.template.js b/cloudformation/AWS-Config.template.js index f84aff9..09113f3 100644 --- a/cloudformation/AWS-Config.template.js +++ b/cloudformation/AWS-Config.template.js @@ -1,4 +1,5 @@ import cf from '@openaddresses/cloudfriend'; +import Rules from './lib/rules.js'; import Config from './lib/config.js'; export default cf.merge({ @@ -9,4 +10,4 @@ export default cf.merge({ Type: 'String' } } -}, Config); +}, Config, Rules); diff --git a/cloudformation/lib/config.js b/cloudformation/lib/config.js index 2c1267d..b8b336f 100644 --- a/cloudformation/lib/config.js +++ b/cloudformation/lib/config.js @@ -93,23 +93,6 @@ const resources = { ZipFile: String(fs.readFileSync(new URL('../../index.js', import.meta.url))) } } - }, - RequiredTags: { - Type: "AWS::Config::ConfigRule", - Properties: { - ConfigRuleName: "Required-Tags", - Description: "This rule ensures resources have required tags", - InputParameters: { - tag1Key: "Project", - tag2Key: "Client", - tag3Key: "Owner" - }, - EvaluationModes: [{ Mode: 'DETECTIVE' }], - Source: { - SourceIdentifier: 'REQUIRED_TAGS', - Owner: "AWS" - } - } } } }; diff --git a/cloudformation/lib/rules.js b/cloudformation/lib/rules.js new file mode 100644 index 0000000..4461acd --- /dev/null +++ b/cloudformation/lib/rules.js @@ -0,0 +1,75 @@ +import cf from '@openaddresses/cloudfriend'; + +const resources = { + Resources: { + CloudformationDrift: { + Type: "AWS::Config::ConfigRule", + Properties: { + ConfigRuleName: "Cloudformation-Drift", + Description: "This rule ensures cloudformation templates don't drift", + InputParameters: { + cloudformationRoleArn: cf.getAtt('CloudformationDriftRole', 'Arn') + }, + MaximumExecutionFrequency: 'Six_Hours', + Scope: { + ComplianceResourceTypes: [ 'AWS::CloudFormation::Stack' ], + }, + Source: { + SourceIdentifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK', + Owner: "AWS" + } + } + }, + CloudformationDriftRole: { + Type: "AWS::IAM::Role", + Properties: { + RoleName: cf.join([cf.stackName, '-cloudformation-drift']), + Description: "IAM role for AWS Config to access CloudFormation drift detection", + AssumeRolePolicyDocument: { + Version: "2012-10-17", + Statement: [{ + Effect: "Allow", + Principal: { + Service: "config.amazonaws.com" + }, + Action: ["sts:AssumeRole"] + }] + }, + Policies: [{ + PolicyName: cf.join([cf.stackName, '-cloudformation-drift']), + PolicyDocument: { + Version: "2012-10-17", + Statement: [{ + Effect: "Allow", + Action: [ + "cloudformation:DetectStackResourceDrift", + "cloudformation:DetectStackDrift", + "cloudformation:DescribeStackDriftDetectionStatus" + ], + Resource: cf.join(['arn:', cf.partition, ':cloudformation:', cf.region, ':', cf.accountId, ':*']) + }] + }, + }] + } + }, + RequiredTags: { + Type: "AWS::Config::ConfigRule", + Properties: { + ConfigRuleName: "Required-Tags", + Description: "This rule ensures resources have required tags", + InputParameters: { + tag1Key: "Project", + tag2Key: "Client", + tag3Key: "Owner" + }, + EvaluationModes: [{ Mode: 'DETECTIVE' }], + Source: { + SourceIdentifier: 'REQUIRED_TAGS', + Owner: "AWS" + } + } + } + } +}; + +export default resources; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d85f4a4 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,38 @@ +import js from "@eslint/js"; +import nodePlugin from "eslint-plugin-n"; + +export default [ + js.configs.recommended, + nodePlugin.configs["flat/recommended-module"], + { + "rules": { + "no-console": 0, + "arrow-parens": [ "error", "always" ], + "no-var": "error", + "prefer-const": "error", + "array-bracket-spacing": [ "error", "never" ], + "comma-dangle": [ "error", "never" ], + "computed-property-spacing": [ "error", "never" ], + "eol-last": "error", + "eqeqeq": [ "error", "smart" ], + "indent": [ "error", 4, { "SwitchCase": 1 } ], + "no-confusing-arrow": [ "error", { "allowParens": false } ], + "no-extend-native": "error", + "no-mixed-spaces-and-tabs": "error", + "func-call-spacing": [ "error", "never" ], + "no-trailing-spaces": "error", + "no-unused-vars": "error", + "no-use-before-define": [ "error", "nofunc" ], + "object-curly-spacing": [ "error", "always" ], + "prefer-arrow-callback": "error", + "quotes": [ "error", "single", "avoid-escape" ], + "semi": [ "error", "always" ], + "space-infix-ops": "error", + "spaced-comment": [ "error", "always" ], + "keyword-spacing": [ "error", { "before": true, "after": true } ], + "template-curly-spacing": [ "error", "never" ], + "semi-spacing": "error", + "strict": "error", + } + } +]