--- #----------------------------------------------------------------------------------------------------------------------- # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance # with the License. A copy of the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions # and limitations under the License. #----------------------------------------------------------------------------------------------------------------------- AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: CDF Commands Service Globals: Api: OpenApiVersion: 3.0.1 Parameters: ApplicationConfigurationOverride: Description: This allows you to override any application configuration. It should consists of a text-based content with a structure and syntax comprising key–value pairs for properties. Any configurations contained in this will override the configurations found and merged from the default .env files. Type: String Environment: Description: Name of environment. Used to name the created resources. Type: String MinLength: 1 TemplateSnippetS3UriBase: Description: | S3 uri of directory where template snippets are stored for the account. Type: String MinLength: 1 AuthType: Description: Authorization type to apply to the API gateway endpoints Type: String Default: None AllowedValues: - None - Private - Cognito - LambdaRequest - LambdaToken - ApiKey - IAM MinLength: 1 ApiGatewayDefinitionTemplate: Description: | Name of the API Gateway Cloudformation definition along with the authorization method to use. Use one of the provided templates to implement no auth, private, api key, lambda request, lamdba token, or Cognito auth, or modify one to meet your own authentization requirements. The template must exist within the provided TemplateSnippetS3UriBase location. Type: String MinLength: 1 VpcId: Description: ID of VPC to deploy the API into. Only required if AuthType = 'Private'. Type: String CDFSecurityGroupId: Description: ID of an existing CDF security group to deploy the API into. Only required if AuthType = 'Private'. Type: String PrivateSubNetIds: Description: Comma delimited list of private subnetIds to deploy the API into. Only required if AuthType = 'Private'. Type: CommaDelimitedList PrivateApiGatewayVPCEndpoint: Description: VPC endpoint. Only required if AuthType = 'Private'. Type: String CognitoUserPoolArn: Description: Cognito user pool arn. Only required if AuthType is set to 'Cognito'. Type: String Default: 'N/A' AuthorizerFunctionArn: Description: Lambda authorizer function arn. Only required if AuthType is set to 'LambdaRequest' or 'LambdaToken'. Type: String Default: 'N/A' BucketName: Description: Name of bucket to store command information. Type: String MinLength: 1 PresignedUrlMQTTTopic: Description: The MQTT topic where pre-signed url requests are sent/received from/to a device. Default: cdf/commands/presignedurl/+/+/+ Type: String MinLength: 1 AssetLibraryFunctionName: Description: AssetLibrary REST API function name Type: String Default: '' ProvisioningFunctionName: Description: Provisioning REST API function name Type: String Default: '' CustomResourceLambdaArn: Description: Custom resource lambda arn Type: String MinLength: 1 KmsKeyId: Description: The KMS key ID used to encrypt DynamoDB. Type: String EnableApiGatewayAccessLogs: Description: Enales API gateway Access Logging, defaults to false if not specified. Type: String Default: 'false' AllowedValues: - 'true' - 'false' MinLength: 1 Conditions: DeployInVPC: !Not [!Equals [!Ref VpcId, 'N/A']] DeployWithLambdaAuth: !Or [!Equals [!Ref AuthType, 'LambdaRequest'], !Equals [!Ref AuthType, 'LambdaToken']] KmsKeyIdProvided: !Not [!Equals [!Ref KmsKeyId, '']] EnableApiGatewayAccessLogs: !Equals [!Ref EnableApiGatewayAccessLogs, 'true'] Resources: ApiGatewayApi: 'Fn::Transform': Name: 'AWS::Include' Parameters: Location: !Sub '${TemplateSnippetS3UriBase}${ApiGatewayDefinitionTemplate}' DependsOn: - RESTLambdaFunction ApiGatewayAuthorizerInvokeRole: Condition: DeployWithLambdaAuth 'Fn::Transform': Name: 'AWS::Include' Parameters: Location: !Sub '${TemplateSnippetS3UriBase}cfn-role-lambdaRequestAuthInvokerRole.yaml' DependsOn: - RESTLambdaFunction ApiGatewayAccessLogGroup: Condition: EnableApiGatewayAccessLogs Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Sub 'cdf-commands-apigatewayaccesslogs-${Environment}' PresignedUrlMQTTRule: Type: 'AWS::IoT::TopicRule' Properties: TopicRulePayload: Actions: - Lambda: FunctionArn: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${PresignedUrlLambdaFunction}' Description: 'Pre-signed url requests (CDF Commands service)' AwsIotSqlVersion: '2016-03-23' RuleDisabled: 'false' Sql: !Sub "SELECT topic(4) as commandId, topic(5) as thingName, * FROM '${PresignedUrlMQTTTopic}'" PresignedUrlLambdaFunctionInvocationPermission: Type: AWS::Lambda::Permission Properties: SourceArn: !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${PresignedUrlMQTTRule}' Action: lambda:InvokeFunction Principal: iot.amazonaws.com FunctionName: !GetAtt PresignedUrlLambdaFunction.Arn SourceAccount: !Ref AWS::AccountId JobLifecycleMQTTRule: Type: 'AWS::IoT::TopicRule' Properties: TopicRulePayload: Actions: - Lambda: FunctionArn: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${JobLifecycleLambdaFunction}' Description: 'Job lifecycle rule (CDF Commands service)' AwsIotSqlVersion: '2016-03-23' RuleDisabled: 'false' Sql: !Sub "SELECT topic(4) as jobId, topic(5) as jobEvent, * FROM '$aws/events/job/+/+'" JobLifecycleMQTTRuleInvocationPermission: Type: AWS::Lambda::Permission Properties: SourceArn: !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${JobLifecycleMQTTRule}' Action: lambda:InvokeFunction Principal: iot.amazonaws.com FunctionName: !GetAtt JobLifecycleLambdaFunction.Arn SourceAccount: !Ref AWS::AccountId KmsPolicy: Type: AWS::IAM::ManagedPolicy Condition: KmsKeyIdProvided Properties: Description: 'cdf-provisioning policy for accessing KMS' Path: '/cdf/commands/' PolicyDocument: Version: '2012-10-17' Statement: - Action: - 'kms:Decrypt' Effect: Allow Resource: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${KmsKeyId}' ApplicationPolicies: Type: 'AWS::IAM::ManagedPolicy' Metadata: cfn_nag: rules_to_suppress: - id: W13 reason: 'Lambda provided permissions to manage resources in iot core' Properties: Description: 'cdf-commands application policies' Path: '/' PolicyDocument: Version: '2012-10-17' Statement: - Sid: 'iotjobs' Action: - 'iot:CreateJob' - 'iot:CreateThingGroup' - 'iot:DescribeJob' - 'iot:GetJobDocument' - 'iot:ListJobExecutionsForJob' - 'iot:ListJobExecutionsForThing' - 'iot:DescribeJobExecution' - 'iot:AssociateTargetsWithJob' Effect: Allow Resource: - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:job/*' - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:thing/*' - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:thinggroup/*' - Sid: 'dynamodb' Action: - 'dynamodb:BatchGetItem' - 'dynamodb:GetItem' - 'dynamodb:Query' - 'dynamodb:Scan' - 'dynamodb:BatchWriteItem' - 'dynamodb:PutItem' - 'dynamodb:UpdateItem' - 'dynamodb:DeleteItem' Effect: Allow Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/cdf-commands-*' - Sid: 'iampassrole' Action: - 'iam:PassRole' Effect: Allow Resource: !Sub 'arn:aws:iam::${AWS::AccountId}:role/cdf-commands-*' - Sid: 's3bucket' Action: - 's3:ListBucket' Effect: Allow Resource: - !Sub 'arn:aws:s3:::${BucketName}' - Sid: 's3objects' Action: - 's3:Get*' - 's3:Put*' Effect: Allow Resource: - !Sub 'arn:aws:s3:::${BucketName}/*' - Sid: 'mqtt' Action: - 'iot:Publish' Effect: Allow Resource: - !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/cdf/commands/*' - Sid: lambda Action: - 'lambda:Invoke*' Effect: Allow Resource: - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${AssetLibraryFunctionName}' - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ProvisioningFunctionName}' PresignedUrlLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: Effect: Allow Principal: Service: - lambda.amazonaws.com - iot.amazonaws.com Action: sts:AssumeRole Path: '/' ManagedPolicyArns: - !Ref ApplicationPolicies - !If [KmsKeyIdProvided, !Ref KmsPolicy, !Ref 'AWS::NoValue'] - arn:aws:iam::aws:policy/AWSLambdaExecute - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole DependsOn: - ApplicationPolicies JobLifecycleLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: Effect: Allow Principal: Service: - lambda.amazonaws.com - iot.amazonaws.com Action: sts:AssumeRole Path: '/' ManagedPolicyArns: - !Ref ApplicationPolicies - !If [KmsKeyIdProvided, !Ref KmsPolicy, !Ref 'AWS::NoValue'] - arn:aws:iam::aws:policy/AWSLambdaExecute - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole DependsOn: - ApplicationPolicies RESTLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: Effect: Allow Principal: Service: - lambda.amazonaws.com - iot.amazonaws.com Action: sts:AssumeRole Path: '/' ManagedPolicyArns: - !Ref ApplicationPolicies - !If [KmsKeyIdProvided, !Ref KmsPolicy, !Ref 'AWS::NoValue'] - arn:aws:iam::aws:policy/AWSLambdaExecute - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole DependsOn: - ApplicationPolicies PresignedUrlLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub 'cdf-commands-presignedUrls-${Environment}' CodeUri: ../bundle.zip Handler: packages/services/commands/dist/presignedurl_iot_rule.presignedurl_rule_handler MemorySize: 512 Role: !GetAtt PresignedUrlLambdaExecutionRole.Arn Runtime: nodejs16.x Timeout: 30 Environment: Variables: APP_CONFIG_DIR: 'packages/services/commands/dist/config' APP_CONFIG: !Ref ApplicationConfigurationOverride AWS_S3_BUCKET: !Ref BucketName AWS_S3_ROLEARN: !Sub '${PresignedUrlLambdaExecutionRole.Arn}' TABLES_TEMPLATES: !Ref TemplatesTable TABLES_JOBS: !Ref JobsTable ASSETLIBRARY_API_FUNCTION_NAME: !Ref AssetLibraryFunctionName PROVISIONING_API_FUNCTION_NAME: !Ref ProvisioningFunctionName Tracing: Active JobLifecycleLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub 'cdf-commands-jobLifecycle-${Environment}' CodeUri: ../bundle.zip Handler: packages/services/commands/dist/job_lifecycle_iot_rule.job_lifecycle_rule_handler MemorySize: 512 Role: !GetAtt JobLifecycleLambdaExecutionRole.Arn Runtime: nodejs16.x Timeout: 30 Environment: Variables: APP_CONFIG_DIR: 'packages/services/commands/dist/config' APP_CONFIG: !Ref ApplicationConfigurationOverride AWS_S3_BUCKET: !Ref BucketName AWS_S3_ROLEARN: !Sub '${PresignedUrlLambdaExecutionRole.Arn}' TABLES_TEMPLATES: !Ref TemplatesTable TABLES_JOBS: !Ref JobsTable ASSETLIBRARY_API_FUNCTION_NAME: !Ref AssetLibraryFunctionName PROVISIONING_API_FUNCTION_NAME: !Ref ProvisioningFunctionName Tracing: Active RESTLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub 'cdf-commands-rest-${Environment}' CodeUri: ../bundle.zip Handler: packages/services/commands/dist/lambda_proxy.handler MemorySize: 512 Role: !GetAtt RESTLambdaExecutionRole.Arn Runtime: nodejs16.x Timeout: 30 Environment: Variables: APP_CONFIG_DIR: 'packages/services/commands/dist/config' APP_CONFIG: !Ref ApplicationConfigurationOverride AWS_S3_BUCKET: !Ref BucketName AWS_S3_ROLEARN: !Sub '${PresignedUrlLambdaExecutionRole.Arn}' TABLES_TEMPLATES: !Ref TemplatesTable TABLES_JOBS: !Ref JobsTable ASSETLIBRARY_API_FUNCTION_NAME: !Ref AssetLibraryFunctionName PROVISIONING_API_FUNCTION_NAME: !Ref ProvisioningFunctionName AWS_IOT_ENDPOINT: !Ref IotEndpoint Tracing: Active VpcConfig: Fn::If: - DeployInVPC - SubnetIds: !Ref PrivateSubNetIds SecurityGroupIds: - !Ref CDFSecurityGroupId - Ref: AWS::NoValue Events: ProxyApiRoot: Type: Api Properties: RestApiId: !Ref ApiGatewayApi Path: / Method: ANY ProxyApiGreedy: Type: Api Properties: RestApiId: !Ref ApiGatewayApi Path: /{proxy+} Method: ANY TemplatesTable: Type: AWS::DynamoDB::Table Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: 'safely naming this resources to be unique by environment' - id: W73 reason: 'Can be decided by the customer, to specify the billing mode' Properties: TableName: !Sub 'cdf-commands-templates-${Environment}' PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: Fn::If: - KmsKeyIdProvided - KMSMasterKeyId: !Ref KmsKeyId SSEEnabled: true SSEType: KMS - Ref: AWS::NoValue AttributeDefinitions: - AttributeName: 'templateId' AttributeType: 'S' KeySchema: - AttributeName: 'templateId' KeyType: 'HASH' ProvisionedThroughput: ReadCapacityUnits: '5' WriteCapacityUnits: '5' JobsTable: Type: AWS::DynamoDB::Table Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: 'safely naming this resources to be unique by environment' - id: W73 reason: 'Can be decided by the customer, to specify the billing mode' Properties: TableName: !Sub 'cdf-commands-jobs-${Environment}' PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: Fn::If: - KmsKeyIdProvided - KMSMasterKeyId: !Ref KmsKeyId SSEEnabled: true SSEType: KMS - Ref: AWS::NoValue AttributeDefinitions: - AttributeName: 'commandId' AttributeType: 'S' - AttributeName: 'jobId' AttributeType: 'S' KeySchema: - AttributeName: 'commandId' KeyType: 'HASH' ProvisionedThroughput: ReadCapacityUnits: '5' WriteCapacityUnits: '5' GlobalSecondaryIndexes: - IndexName: !Sub 'cdf-commands-jobs-byJobId' KeySchema: - AttributeName: 'jobId' KeyType: 'HASH' Projection: ProjectionType: INCLUDE NonKeyAttributes: - 'commandId' ProvisionedThroughput: ReadCapacityUnits: '5' WriteCapacityUnits: '5' IotEndpoint: Type: Custom::IotEndpoint Version: 1.0 Properties: ServiceToken: !Ref CustomResourceLambdaArn IotEvents: Type: Custom::IotEvents Version: 1.0 Properties: ServiceToken: !Ref CustomResourceLambdaArn Outputs: RESTLambdaArn: Description: REST API Lambda Arn Value: !Sub '${RESTLambdaFunction.Arn}' Export: Name: !Sub 'cdf-commands-${Environment}-RESTLambdaArn' RestApiFunctionName: Description: Commands REST API lambda function name Value: !Ref RESTLambdaFunction Export: Name: !Sub 'cdf-commands-${Environment}-restApiFunctionName' ApiGatewayUrl: Description: Commands REST API URL Value: !Sub 'https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/Prod' Export: Name: !Sub 'cdf-commands-${Environment}-apigatewayurl' ApiGatewayHost: Description: Commands REST API host Value: !Sub '${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com' Export: Name: !Sub 'cdf-commands-${Environment}-apigatewayhost'