constructor()

in deployment/custom-deployment/lib/smart-product-api.ts [89:844]


  constructor(parent: cdk.Construct, name: string, props: ApiProps) {
    super(parent, name);

    //=============================================================================================
    // Resources
    //=============================================================================================
    //---------------------------------------------------------------------------------------------
    // Custom Resource
    //---------------------------------------------------------------------------------------------
    // Helper IoT Search Index Policy
    const helperIoTSearchIndexPolicy = new iam.Policy(this, 'HelperIoTSearchIndexPolicy', {
      statements: [new iam.PolicyStatement({
        actions: [
          'iot:UpdateIndexingConfiguration'
        ],
        resources: ['*']
      })]
    })
    const helperIoTSearchIndexPolicyResource = helperIoTSearchIndexPolicy.node.findChild('Resource') as iam.CfnPolicy;
    helperIoTSearchIndexPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${props.helperFunctionRole.roleName} to update IoT Search Index.`
        }]
      }
    }
    helperIoTSearchIndexPolicy.attachToRole(props.helperFunctionRole);

    const _updateIoTSearchIndex = new cfn.CustomResource(this, 'UpdateIoTSearchIndex', {
      provider: props.helperFunction,
      resourceType: 'Custom::UpdateIoTSearchIndex',
      properties: {
        Region: `${cdk.Aws.REGION}`,
        CustomAction: 'updateIoTSearchIndex'
      }
    })
    _updateIoTSearchIndex.node.addDependency(helperIoTSearchIndexPolicy.node.findChild('Resource') as cdk.Resource)

    // Helper DynamoDB Policy
    const helperDynamoDBPolicy = new iam.Policy(this, 'helperDynamoDBPolicy', {
      statements: [new iam.PolicyStatement({
        actions: [
          'dynamodb:PutItem'
        ],
        resources: [`${props.settingsTable.tableArn}`]
      })]
    })
    helperDynamoDBPolicy.attachToRole(props.helperFunctionRole);

    const _saveDDBItem = new cfn.CustomResource(this, 'SaveDdbItem', {
      provider: props.helperFunction,
      resourceType: 'Custom::SaveDdbItem',
      properties: {
        Region: `${cdk.Aws.REGION}`,
        CustomAction: 'SaveDdbItem',
        ddbTable: props.settingsTable.tableName,
        ddbItem: {
          settingId: 'app-config',
          setting: {
            idp: props.userPool.userPoolId
          }
        }
      }
    })
    _saveDDBItem.node.addDependency(helperDynamoDBPolicy.node.findChild('Resource') as cdk.Resource)

    //---------------------------------------------------------------------------------------------
    // Microservices
    //---------------------------------------------------------------------------------------------
    // admin - SmartProductAdminRole
    const adminServiceRole = new iam.Role(this, 'AdminServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const adminService = new lambda.Function(this, 'AdminService', {
      functionName: "SmartProductAPI-AdminService",
      description: "Smart Product administration API microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'AdminBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-admin-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(60),
      memorySize: 256,
      role: adminServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        SETTINGS_TBL: props.settingsTable.tableName
      },
    })

    // registration - SmartProductRegistrationRole
    const registrationServiceRole = new iam.Role(this, 'RegistrationServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const registrationService = new lambda.Function(this, 'RegistrationService', {
      functionName: "SmartProductAPI-RegistrationService",
      description: "Smart Product Solution registration API microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'RegistrationBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-registration-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(60),
      memorySize: 256,
      role: registrationServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        REGISTRATION_TBL: props.registrationTable.tableName,
        REFERENCE_TBL: props.referenceTable.tableName,
        THING_TYPE: 'SmartProduct',
        solutionId: props.solutionId,
        solutionUuid: props.solutionUuid,
        anonymousData: props.anonymousData
      }
    })

    // event - SmartProductEventRole
    const eventServiceRole = new iam.Role(this, 'EventServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const eventService = new lambda.Function(this, 'EventService', {
      functionName: "SmartProductAPI-EventService",
      description: "Smart Product Solution event API microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'EventBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-event-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(60),
      memorySize: 256,
      role: eventServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        SETTINGS_TBL: props.settingsTable.tableName,
        REGISTRATION_TBL: props.registrationTable.tableName,
        EVENTS_TBL: props.eventsTable.tableName
      }
    })

    // Command - SmartProductCommandRole
    const commandServiceRole = new iam.Role(this, 'CommandServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const commandService = new lambda.Function(this, 'CommandService', {
      functionName: "SmartProductAPI-CommandService",
      description: "Smart Product Solution command API microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'CommandBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-command-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(300),
      memorySize: 256,
      role: commandServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        REGISTRATION_TBL: props.registrationTable.tableName,
        COMMANDS_TBL: props.commandTable.tableName,
        solutionId: props.solutionId,
        solutionUuid: props.solutionUuid,
        anonymousData: props.anonymousData
      }
    })

    // Status - SmartProductStatusRole
    const statusServiceRole = new iam.Role(this, 'StatusServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const statusService = new lambda.Function(this, 'StatusService', {
      functionName: "SmartProductAPI-StatusService",
      description: "Smart Product Solution status API microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'StatusBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-status-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(300),
      memorySize: 256,
      role: statusServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        REGISTRATION_TBL: props.registrationTable.tableName
      }
    })

    // Device - SmartProductDeviceRole
    const deviceServiceRole = new iam.Role(this, 'DeviceServiceRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com')
    })
    const deviceService = new lambda.Function(this, 'DeviceService', {
      functionName: "SmartProductAPI-DeviceService",
      description: "Smart Product Solution device microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'DeviceBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-device-service.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(60),
      memorySize: 256,
      role: deviceServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        IDP: props.userPool.userPoolId,
        REGISTRATION_TBL: props.registrationTable.tableName
      }
    })

    // Command Status
    const commandStatusService = new lambda.Function(this, 'CommandStatusService', {
      functionName: "SmartProductAPI-CommandStatusService",
      description: "Smart Product Solution command status microservice",
      code: new lambda.S3Code(
        s3.Bucket.fromBucketArn(this, 'CommandStatusBuildOutputBucket', `arn:aws:s3:::${process.env.BUILD_OUTPUT_BUCKET}`),
        `smart-product-solution/${props.solutionVersion}/smart-product-command-status.zip`
      ),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(60),
      memorySize: 256,
      role: commandServiceRole,
      environment: {
        LOGGING_LEVEL: '2',
        COMMANDS_TBL: props.commandTable.tableName
      }
    })

    //---------------------------------------------------------------------------------------------
    // API Gateway
    //---------------------------------------------------------------------------------------------
    const apiLambdaExecRole = new iam.Role(this, 'ApiLambdaExecRole', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com')
    })

    const api = new apigateway.RestApi(this, 'smart-product-api', {
      restApiName: 'SmartProductAPI',
      deploy: false,
    });

    const apiDeployment = new apigateway.Deployment(this, 'Deployment', {
      api,
      description: 'Production'
    });
    const apiDeploymentResource = apiDeployment.node.findChild('Resource') as apigateway.CfnDeployment;
    apiDeploymentResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W45',
          reason: 'The access logging is enabled at API Gateway stage.'
        }]
      }
    };
    api.deploymentStage = new apigateway.Stage(this, 'Stage', {
      deployment: apiDeployment,
      description: 'Smart Product Stage',
      stageName: 'prod'
    });
    const apiStageResource = api.deploymentStage.node.findChild('Resource') as apigateway.CfnStage;
    apiStageResource.addPropertyOverride('AccessLogSetting', {
      DestinationArn: `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:API-Gateway-Execution-Logs_${api.restApiId}/prod`,
      Format: `{ "requestId": "$context.requestId",
        "ip": "$context.identity.sourceIp",
        "caller": "$context.identity.caller",
        "user": "$context.identity.user",
        "userAgent": "$context.identity.userAgent",
        "requestTime": "$context.requestTime",
        "httpMethod": "$context.httpMethod",
        "resourcePath": "$context.resourcePath",
        "status": "$context.status",
        "protocol": "$context.protocol",
        "responseLength": "$context.responseLength"
      }`.replace(/\n/g, '')
    });

    const authorizer = new apigateway.CfnAuthorizer(this, 'Authorizer', {
      identitySource: 'method.request.header.Authorization',
      name: 'Authorization',
      type: 'COGNITO_USER_POOLS',
      providerArns: [props.userPool.userPoolArn],
      restApiId: api.restApiId
    });

    const adminRes = api.root.addResource("admin");
    apiDeployment.node.addDependency(adminRes.node.findChild('Resource') as cdk.Resource);

    //admin/settings/config/{settingId}
    const configRes = adminRes.addResource("settings").addResource("config").addResource("{settingId}");

    //registration
    const registrationRes = api.root.addResource("registration");

    //devices
    const devicesRes = api.root.addResource("devices");

    //devices/events
    const eventsRes = devicesRes.addResource("events");

    //devices/alerts
    const alertsRes = devicesRes.addResource("alerts");

    //devices/alerts/count
    const countRes = alertsRes.addResource("count");

    //devices/{deviceId}
    const deviceRes = devicesRes.addResource("{deviceId}");

    //devices/{deviceId}/commands
    const commandsRes = deviceRes.addResource("commands");

    //devices/{deviceId}/commands/{commandId}
    const commandRes = commandsRes.addResource("{commandId}");

    //devices/{deviceId}/events
    const deviceEventsRes = deviceRes.addResource("events");

    //devices/{deviceId}/events/{eventId}
    const deviceEventRes = deviceEventsRes.addResource("{eventId}");

    //devices/{deviceId}/status
    const statusRes = deviceRes.addResource("status");

    addMethod(
      [
        { apiResource: configRes, lambdaFunction: adminService},
        { apiResource: registrationRes, lambdaFunction: registrationService},
        { apiResource: devicesRes, lambdaFunction: deviceService},
        { apiResource: eventsRes, lambdaFunction: eventService},
        { apiResource: alertsRes, lambdaFunction: eventService},
        { apiResource: countRes, lambdaFunction: eventService},
        { apiResource: deviceRes, lambdaFunction: deviceService},
        { apiResource: commandsRes, lambdaFunction: commandService},
        { apiResource: commandRes, lambdaFunction: commandService},
        { apiResource: deviceEventsRes, lambdaFunction: eventService},
        { apiResource: deviceEventRes, lambdaFunction: eventService},
        { apiResource: statusRes, lambdaFunction: statusService}
      ]
      , apiLambdaExecRole, authorizer.ref, apiDeployment);

    //---------------------------------------------------------------------------------------------
    // Others
    //---------------------------------------------------------------------------------------------
    const commandStatusRule = new iot.CfnTopicRule(this, 'CommandStatusRule', {
      ruleName: "SmartProductCommandStatusRule",
      topicRulePayload: {
        actions: [{ lambda: { functionArn: commandStatusService.functionArn } }],
        description: 'Command status for Smart Product Solution.',
        ruleDisabled: false,
        sql: `select * from 'smartproduct/commands/#'`
      }
    })

    //=============================================================================================
    // Permissions and Policies
    //=============================================================================================
    const apiLambdaExecPolicy = new iam.Policy(this, 'apiLambdaExecPolicy', {
      statements: [new iam.PolicyStatement({
        actions: ['lambda:InvokeFunction'],
        resources: [
          adminService.functionArn,
          registrationService.functionArn,
          commandService.functionArn,
          statusService.functionArn,
          eventService.functionArn,
          deviceService.functionArn
        ]
      })]
    })
    apiLambdaExecPolicy.attachToRole(apiLambdaExecRole);

    new lambda.CfnPermission(this, 'LambdaInvokeCommandStatusPermission', {
      functionName: `${commandStatusService.functionArn}`,
      action: 'lambda:InvokeFunction',
      principal: 'iot.amazonaws.com',
      sourceArn: `arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/${commandStatusRule.ruleName}`,
      sourceAccount: cdk.Aws.ACCOUNT_ID
    })

    //---------------------------------------------------------------------------------------------
    // Registration
    //---------------------------------------------------------------------------------------------
    const registrationPolicy = new iam.Policy(this, 'registrationPolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${registrationService.functionName}:*`]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: [
            'dynamodb:DeleteItem',
            'dynamodb:PutItem',
            'dynamodb:Query'
          ],
          resources: [
            `${props.registrationTable.tableArn}`,
            `${props.registrationTable.tableArn}/index/userId-deviceName-index`,
            `${props.registrationTable.tableArn}/index/deviceId-index`
          ]
        }),
        new iam.PolicyStatement({
          actions: ['dynamodb:GetItem'],
          resources: [`${props.referenceTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        }),

        // IoT
        new iam.PolicyStatement({
          actions: ['iot:CreateThing'],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:thing/*`]
        })
      ]
    })
    const registrationPolicyResource = registrationPolicy.node.findChild('Resource') as iam.CfnPolicy;
    registrationPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${registrationServiceRole.roleName} to access its own logs and create things.`
        }]
      }
    }
    registrationPolicy.attachToRole(registrationServiceRole);

    //---------------------------------------------------------------------------------------------
    // Admin
    //---------------------------------------------------------------------------------------------
    const adminPolicy = new iam.Policy(this, 'AdminPolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${adminService.functionName}:*`]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: [
            'dynamodb:GetItem',
            'dynamodb:PutItem'
          ],
          resources: [`${props.settingsTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        })
      ]
    })
    const adminPolicyResource = adminPolicy.node.findChild('Resource') as iam.CfnPolicy;
    adminPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${adminServiceRole.roleName} to access its own logs.`
        }]
      }
    }
    adminPolicy.attachToRole(adminServiceRole);

    //---------------------------------------------------------------------------------------------
    // Event
    //---------------------------------------------------------------------------------------------
    const eventPolicy = new iam.Policy(this, 'EventPolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${eventService.functionName}:*`]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: [
            'dynamodb:GetItem',
            'dynamodb:PutItem',
            'dynamodb:Query'
          ],
          resources: [
            `${props.eventsTable.tableArn}`,
            `${props.eventsTable.tableArn}/index/userId-timestamp-index`,
            `${props.eventsTable.tableArn}/index/deviceId-timestamp-index`
          ]
        }),
        new iam.PolicyStatement({
          actions: ['dynamodb:GetItem'],
          resources: [`${props.settingsTable.tableArn}`]
        }),
        new iam.PolicyStatement({
          actions: [
            'dynamodb:Query',
            'dynamodb:GetItem'
          ],
          resources: [`${props.registrationTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        })
      ]
    })
    const eventPolicyResource = eventPolicy.node.findChild('Resource') as iam.CfnPolicy;
    eventPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${eventServiceRole.roleName} to access its own logs.`
        }]
      }
    }
    eventPolicy.attachToRole(eventServiceRole);

    //---------------------------------------------------------------------------------------------
    // Command
    //---------------------------------------------------------------------------------------------
    const commandPolicy = new iam.Policy(this, 'CommandPolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${commandService.functionName}:*`,
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${commandStatusService.functionName}:*`
          ]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: [
            'dynamodb:GetItem',
            'dynamodb:PutItem',
            'dynamodb:Query',
            'dynamodb:UpdateItem'
          ],
          resources: [
            `${props.commandTable.tableArn}`,
            `${props.commandTable.tableArn}/index/deviceId-updatedAt-index`
          ]
        }),
        new iam.PolicyStatement({
          actions: ['dynamodb:GetItem'],
          resources: [`${props.registrationTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        }),

        // IoT
        new iam.PolicyStatement({
          actions: [
            'iot:DescribeEndpoint',
            'iot:GetThingShadow',
            'iot:UpdateThingShadow',
            'iot:Publish',
          ],
          resources: [`*`]
        })
      ]
    })
    const commandPolicyResource = commandPolicy.node.findChild('Resource') as iam.CfnPolicy;
    commandPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${commandServiceRole.roleName} to access its own logs and exchange information with IoT devices.`
        }]
      }
    }
    commandPolicy.attachToRole(commandServiceRole);

    //---------------------------------------------------------------------------------------------
    // Status
    //---------------------------------------------------------------------------------------------
    const statusPolicy = new iam.Policy(this, 'StatusPolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${statusService.functionName}:*`]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: ['dynamodb:GetItem'],
          resources: [`${props.registrationTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        }),

        // IoT
        new iam.PolicyStatement({
          actions: [
            'iot:DescribeEndpoint',
            'iot:GetThingShadow',
            'iot:SearchIndex'
          ],
          resources: [`*`]
        })
      ]
    })
    const statusPolicyResource = statusPolicy.node.findChild('Resource') as iam.CfnPolicy;
    statusPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${statusServiceRole.roleName} to access its own logs and exchange information with IoT devices.`
        }]
      }
    }
    statusPolicy.attachToRole(statusServiceRole);

    //---------------------------------------------------------------------------------------------
    // Device
    //---------------------------------------------------------------------------------------------
    const devicePolicy = new iam.Policy(this, 'DevicePolicy', {
      statements: [
        // Logs
        new iam.PolicyStatement({
          actions: [
            'logs:CreateLogGroup',
            'logs:CreateLogStream',
            'logs:PutLogEvents'
          ],
          resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/${deviceService.functionName}:*`]
        }),

        // DynamoDB
        new iam.PolicyStatement({
          actions: [
            'dynamodb:GetItem',
            'dynamodb:PutItem'
          ],
          resources: [`${props.registrationTable.tableArn}`]
        }),

        // Cognito
        new iam.PolicyStatement({
          actions: [
            'cognito-idp:AdminGetUser',
            'cognito-idp:AdminListGroupsForUser'
          ],
          resources: [`${props.userPool.userPoolArn}`]
        }),

        // IoT
        new iam.PolicyStatement({
          actions: [
            'iot:SearchIndex',
            'iot:DescribeIndex'
          ],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:index/AWS_Things`]
        }), new iam.PolicyStatement({
          actions: [
            'iot:DeleteThing',
            'iot:DescribeThing',
            'iot:ListThingPrincipals'
          ],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:thing/*`]
        }), new iam.PolicyStatement({
          actions: [
            'iot:DeletePolicy'
          ],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:policy/*`]
        }), new iam.PolicyStatement({
          actions: [
            'iot:DetachThingPrincipal',
            'iot:DeleteCertificate',
            'iot:UpdateCertificate'
          ],
          resources: [`arn:aws:iot:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:cert/*`]
        })
      ]
    })
    const devicePolicyResource = devicePolicy.node.findChild('Resource') as iam.CfnPolicy;
    devicePolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W12',
          reason: `The * resource allows ${deviceServiceRole.roleName} to access its own logs and exchange information with IoT devices.`
        }]
      }
    }
    devicePolicy.attachToRole(deviceServiceRole);

    //=============================================================================================
    // Output
    //=============================================================================================
    this.apiEndpoint = api.url;
    new cdk.CfnOutput(this, 'APIEndpoint', {
      description: 'Smart Product API Endpoint',
      value: api.url
    })
  }