diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 87e506c6..ab86605f 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -38,3 +38,5 @@ jobs: exit 1 - name: Deploy fixtures run: npm run test:integration + - name: Verify compiled templates + run: npx mocha "fixtures/**/verify.test.js" diff --git a/fixtures/circular-dependency/verify.test.js b/fixtures/circular-dependency/verify.test.js new file mode 100644 index 00000000..cf53d7e9 --- /dev/null +++ b/fixtures/circular-dependency/verify.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); +const expect = require('chai').expect; + +const templatePath = path.join( + __dirname, + '.serverless', + 'cloudformation-template-update-stack.json', +); + +describe('circular-dependency fixture — CloudFormation template', () => { + let resources; + + before(() => { + const template = JSON.parse(fs.readFileSync(templatePath, 'utf8')); + resources = template.Resources; + }); + + it('should not use a plain Ref to a Lambda function as an IAM policy Resource', () => { + const iamRoles = Object.values(resources).filter( + (r) => r.Type === 'AWS::IAM::Role', + ); + + const badResources = []; + + iamRoles.forEach((role) => { + const policies = (role.Properties && role.Properties.Policies) || []; + policies.forEach((policy) => { + const statements = (policy.PolicyDocument && policy.PolicyDocument.Statement) || []; + [].concat(statements).forEach((statement) => { + [].concat(statement.Resource || []).forEach((resource) => { + if (resource && typeof resource === 'object' && resource.Ref) { + if (typeof resource.Ref === 'string' && resource.Ref.endsWith('LambdaFunction')) { + badResources.push(resource.Ref); + } + } + }); + }); + }); + }); + + expect(badResources).to.deep.equal( + [], + 'IAM policy Resource(s) use a plain { Ref: LambdaFunction } which resolves to the ' + + 'function name, not its ARN. Use Fn::GetAtt or Fn::Sub to produce a valid ARN. ' + + `Bad refs: ${badResources.join(', ')}`, + ); + }); +});