constructor()

in source/infrastructure/lib/greengrass.ts [57:344]


  constructor(scope: Construct, id: string, props: GreengrassIoTProps) {
    super(scope, id);

    const sourceCodeBucket = props.solutionConfig.sourceCodeBucket;
    const sourceCodePrefix = props.solutionConfig.sourceCodePrefix;

    const thingName = `${Aws.STACK_NAME}-M2C2DeviceGateway`;
    const m2c2DeviceGateway = new CfnThing(this, 'm2c2DeviceGateway', { thingName });
    this.m2c2DeviceGatewayThing = m2c2DeviceGateway.thingName as string;
    this.m2c2DeviceGatewayThingArn = `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/${this.m2c2DeviceGatewayThing}`;

    this.greengrassBucket = new Bucket(this, 'GreegrassBucket', {
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: RemovalPolicy.RETAIN,
      serverAccessLogsBucket: props.s3LoggingBucket,
      serverAccessLogsPrefix: 'm2c2/'
    });
    this.greengrassBucket.addToResourcePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        principals: [new ServicePrincipal('lambda.amazonaws.com')],
        actions: [
          's3:GetObject',
          's3:PutObject'
        ],
        resources: [`${this.greengrassBucket.bucketArn}/*`]
      })
    );

    const ggCertCreatorPolicy = new Policy(this, 'GGCertCreatorPolicy', {
      statements: [
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [
            this.greengrassBucket.bucketArn,
            `${this.greengrassBucket.bucketArn}/*`
          ],
          actions: [
            's3:GetObject',
            's3:PutObject'
          ]
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [`arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/${this.m2c2DeviceGatewayThing}`],
          actions: ['iot:ListThingPrincipals']
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: ['*'],
          actions: [
            'iot:CreateKeysAndCertificate',
            'iot:DescribeEndpoint',
            'iot:UpdateCertificate',
            'iot:UpdateThingShadow',
            'iot:DeleteCertificate'
          ]
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [
            `arn:${Aws.PARTITION}:greengrass:${Aws.REGION}:${Aws.ACCOUNT_ID}:/greengrass/groups/*`
          ],
          actions: [
            'greengrass:ResetDeployments',
            'greengrass:GetGroup'
          ]
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [
            `arn:${Aws.PARTITION}:logs:${Aws.REGION}:${Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`
          ]
        })
      ]
    });
    addCfnSuppressRules(ggCertCreatorPolicy, [
      { id: 'W12', reason: 'The * resource is required for the IoT actions for the Lambda function to preform.' }
    ]);

    const ggCertCreatorRole = new Role(this, 'GGCertCreatorRole', {
      assumedBy: new CompositePrincipal(
        new ServicePrincipal('lambda.amazonaws.com')
      )
    });
    ggCertCreatorRole.attachInlinePolicy(ggCertCreatorPolicy)

    const ggCertCreatorLambda = new LambdaFunction(this, 'GGCertCreatorLambdaCustomResource', {
      description: 'AWS Machine to Cloud connection builder function that creates the certificate, keypair, config, and install script for the Greengrass edge device',
      code: Code.fromBucket(sourceCodeBucket, `${sourceCodePrefix}/custom-resource.zip`),
      handler: 'custom-resource/index.handler',
      role: ggCertCreatorRole,
      runtime: Runtime.NODEJS_14_X,
      timeout: Duration.minutes(1),
      memorySize: 128,
      environment: {
        STACK_NAME: Aws.STACK_NAME,
        SOLUTION_ID: props.solutionConfig.solutionId,
        SOLUTION_VERION: props.solutionConfig.solutionVersion
      }
    });

    const ggCertCreatorCustomResource = new CustomResource(this, 'GGCertCreator', {
      serviceToken: ggCertCreatorLambda.functionArn,
      properties: {
        Resource: 'CreateGGCertAndKeys',
        DestinationBucket: this.greengrassBucket.bucketName,
        ThingArn: this.m2c2DeviceGatewayThingArn
      }
    });
    ggCertCreatorCustomResource.node.addDependency(ggCertCreatorPolicy);

    this.certificateArn = ggCertCreatorCustomResource.getAtt('certificateArn').toString();
    this.certKeyPairS3URL = ggCertCreatorCustomResource.getAtt('generatedS3URL').toString();
    this.certificateId = ggCertCreatorCustomResource.getAtt('certificateId').toString();

    const m2c2CoreDefinition = new CfnCoreDefinition(this, 'm2c2CoreDefinition', { name: `${Aws.STACK_NAME}-M2C2GreengrassGroup_Core` });
    const m2c2CoreDefinitionVersion = new CfnCoreDefinitionVersion(this, 'm2c2CoreDefinitionVersion', {
      coreDefinitionId: m2c2CoreDefinition.attrId,
      cores: [
        {
          id: m2c2CoreDefinition.attrId,
          thingArn: this.m2c2DeviceGatewayThingArn,
          certificateArn: this.certificateArn,
          syncShadow: true
        }
      ]
    });

    const m2c2GreengrassResourcePolicy = new Policy(this, 'M2C2GreengrassResourcePolicy', {
      statements: [
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/GG_*`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/GG_*`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*-gcm`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*-gda`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*-gci`
          ],
          actions: [
            'iot:GetThingShadow',
            'iot:UpdateThingShadow',
            'iot:DeleteThingShadow'
          ]
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/info/*`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/error/*`,
            `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/data/*`
          ],
          actions: ['iot:Publish']
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: ['*'],
          actions: ['greengrass:*']
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [`arn:${Aws.PARTITION}:logs:${Aws.REGION}:${Aws.ACCOUNT_ID}:log-group:/aws/greengrass/*`],
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:DescribeLogStreams',
            'logs:PutLogEvents'
          ]
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: [`arn:${Aws.PARTITION}:kinesis:${Aws.REGION}:${Aws.ACCOUNT_ID}:stream/${props.kinesisStreamName}`],
          actions: ['kinesis:PutRecords']
        }),
        new PolicyStatement({
          effect: Effect.ALLOW,
          resources: ['*'],
          actions: ['iotsitewise:BatchPutAssetPropertyValue']
        })
      ]
    });
    addCfnSuppressRules(m2c2GreengrassResourcePolicy, [
      { id: 'W12', reason: 'Greengrass and IoT Sitewise cannnot specify the resources, so * is used.' },
      { id: 'F4', reason: 'This policy is for Greengrass group to control Greengrass group fully so * action is needed.' }
    ]);

    const m2c2GreengrassResourceRole = new Role(this, 'M2C2GreengrassResourceRole', {
      assumedBy: new CompositePrincipal(
        new ServicePrincipal('greengrass.amazonaws.com')
      )
    });
    m2c2GreengrassResourceRole.attachInlinePolicy(m2c2GreengrassResourcePolicy);

    const m2c2GreegrassGroup = new CfnGroup(this, 'm2c2GreengrassGroup', {
      name: `${Aws.STACK_NAME}-M2C2GreengrassGroup`,
      roleArn: m2c2GreengrassResourceRole.roleArn,
      initialVersion: {
        coreDefinitionVersionArn: m2c2CoreDefinitionVersion.ref
      }
    });

    this.greengrassGroupId = m2c2GreegrassGroup.attrId;

    new CustomResource(this, 'GGDeleteResources', { // NOSONAR: typescript:S1848
      serviceToken: ggCertCreatorLambda.functionArn,
      properties: {
        Resource: 'DeleteGreengrassResources',
        CertificateId: ggCertCreatorCustomResource.getAtt('certificateId'),
        GreengrassGroupId: this.greengrassGroupId,
        ThingName: this.m2c2DeviceGatewayThing
      }
    });

    const m2c2IoTResourcePolicy = new CfnPolicy(this, 'M2C2IoTResourcePolicy', {
      policyDocument: {
        Version: '2012-10-17',
        Statement: [
          {
            Effect: Effect.ALLOW,
            Resource: '*',
            Action: ['greengrass:*']
          },
          {
            Effect: Effect.ALLOW,
            Resource: [`arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:client/${this.m2c2DeviceGatewayThing}*`],
            Action: ['iot:Connect']
          },
          {
            Effect: Effect.ALLOW,
            Resource: [`${this.m2c2DeviceGatewayThingArn}*`],
            Action: [
              'iot:GetThingShadow',
              'iot:UpdateThingShadow',
              'iot:DeleteThingShadow'
            ]
          },
          {
            Effect: Effect.ALLOW,
            Resource: [
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/info/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/error/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/data/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/things/${m2c2DeviceGateway.thingName}*/shadow/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/sitewise/gateways/*/diagnostics`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/sitewise/things/*/connectors/*/configuration/*`
            ],
            Action: ['iot:Publish']
          },
          {
            Effect: Effect.ALLOW,
            Resource: [
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/m2c2/job/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/things/${m2c2DeviceGateway.thingName}*/shadow/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/sitewise/gateways/*/diagnostics`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/$aws/sitewise/things/*/connectors/*/configuration/*`
            ],
            Action: ['iot:Receive']
          },
          {
            Effect: Effect.ALLOW,
            Resource: [
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topicfilter/m2c2/job/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topicfilter/$aws/things/${m2c2DeviceGateway.thingName}*/shadow/*`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topicfilter/$aws/sitewise/gateways/*/diagnostics`,
              `arn:${Aws.PARTITION}:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topicfilter/$aws/sitewise/things/*/connectors/*/configuration/*`
            ],
            Action: ['iot:Subscribe']
          }
        ]
      }
    });
    addCfnSuppressRules(m2c2IoTResourcePolicy, [
      { id: 'W38', reason: 'The *  action is a placeholder for Greengrass until further testing is performed.' },
      { id: 'W39', reason: 'The * resource on its permission policy allows to manipulate Greegrass resource.' }
    ]);

    new CfnPolicyPrincipalAttachment(this, 'M2C2PolicyPrincipalAttachment', { // NOSONAR: typescript:S1848
      policyName: m2c2IoTResourcePolicy.ref,
      principal: this.certificateArn
    });
  }