def __init__(self, scope: Construct, construct_id: str, eks_cluster, env, **kwargs) -> None: super().__init__(scope, construct_id, env=env, **kwargs) statement = EksWorkerRoleStatements(env.account) oidc_url = eks_cluster.cluster_open_id_connect_issuer_url iam_oic = iam.OpenIdConnectProvider( self, construct_id, url=oidc_url, client_ids=['sts.amazonaws.com'] ) Tags.of(iam_oic).add(key='cfn.eks-dev.stack', value='iam-pid-stack') oidc_provider = re.sub("https://", "", oidc_url) def string_like(name_space, sa_name): string = CfnJson( self, f'JsonCondition{sa_name}', value={ f'{oidc_provider}:sub': f'system:serviceaccount:{name_space}:{sa_name}', f'{oidc_provider}:aud': 'sts.amazonaws.com' } ) return string oic_role = iam.Role( self, 'EksIAMServiceAccountRole', role_name='sel-eks-oic-dev-sa', assumed_by=iam.FederatedPrincipal( federated=f'arn:aws:iam::{env.account}:oidc-provider/{oidc_provider}', conditions={'StringEquals': string_like('dev', 'sel-eks-sa')}, assume_role_action='sts:AssumeRoleWithWebIdentity' ) ) oic_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name('AmazonS3ReadOnlyAccess')) oic_role.add_to_policy(statement.cognito_power_statement()) oic_role.add_to_policy(statement.admin_statement()) oic_role.add_to_policy(statement.ddb_statement()) daemonset_role = iam.Role( self, 'DaemonsetIamRole', role_name='sel-eks-oic-daemonset-sa', assumed_by=iam.FederatedPrincipal( federated=f'arn:aws:iam::{env.account}:oidc-provider/{oidc_provider}', conditions={'StringEquals': string_like('kube-system', 'aws-node')}, assume_role_action='sts:AssumeRoleWithWebIdentity' ) ) daemonset_role.add_to_policy(statement.eks_cni())
def autoscaler_role(self, policy: iam.Policy) -> iam.Role: oidc_arn = cdk.CfnJson( self, "oidc-provider", value=self.cluster.open_id_connect_provider. open_id_connect_provider_arn, ) oidc_arn_condition = cdk.CfnJson( self, "oidc-provider-condition", value={ f"{self.cluster.open_id_connect_provider.open_id_connect_provider_issuer}:sub": "system:serviceaccount:kube-system:cluster-autoscaler" }, ) role = iam.Role( self, "ClusterAutoscalerRole", assumed_by=iam.FederatedPrincipal( oidc_arn.value.to_string(), {"StringEquals": oidc_arn_condition}, "sts:AssumeRoleWithWebIdentity", ), ) role.attach_inline_policy(policy) return role
def create_github_action_role(self, bucket_arn: str) -> None: self.github_action_role = aws_iam.Role( self, "github_actions_role", role_name="amplifyios-githubaction-integtest", description= "Role assumed by GitHub action in the amplify-ios integration test", assumed_by=aws_iam.FederatedPrincipal( self.oidc.open_id_connect_provider_arn, assume_role_action="sts:AssumeRoleWithWebIdentity", conditions={ "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:aws-amplify/amplify-ios:*" } })) bucket_resource = bucket_arn + "/testconfiguration/*" bucket_policy = aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=["s3:GetObject", "s3:ListBucket"], resources=[bucket_arn, bucket_resource], ) self.github_action_role.add_to_policy(bucket_policy)
def SamlFederatedPrincipal(federated: str) -> aws_iam.FederatedPrincipal: return aws_iam.FederatedPrincipal( federated=federated, conditions={ 'StringEquals': { 'SAML:aud': 'https://signin.aws.amazon.com/saml' } }, assume_role_action='sts:AssumeRoleWithSAML')
def __init__(self, scope: core.Construct, id: str, common_stack: CommonStack, **kwargs) -> None: super().__init__(scope, id, **kwargs) self._supported_in_region = self.is_service_supported_in_region() identity_pool = cognito.CfnIdentityPool( self, "pinpoint_integ_test_android", allow_unauthenticated_identities=True ) unauthenticated_role = iam.Role( self, "CognitoDefaultUnauthenticatedRole", assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", { "StringEquals": {"cognito-identity.amazonaws.com:aud": identity_pool.ref}, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, "sts:AssumeRoleWithWebIdentity", ), ) unauthenticated_role.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "cognito-sync:*", "iot:Connect", "iot:Publish", "iot:Subscribe", "iot:Receive", "iot:GetThingShadow", "iot:DescribeEndpoint", "iot:CreateKeysAndCertificate", "iot:CreatePolicy", "iot:AttachPolicy", ], resources=["*"], ) ) cognito.CfnIdentityPoolRoleAttachment( self, "DefaultValid", identity_pool_id=identity_pool.ref, roles={"unauthenticated": unauthenticated_role.role_arn}, ) self._parameters_to_save = {"identity_pool_id": identity_pool.ref} self.save_parameters_in_parameter_store(platform=Platform.ANDROID) stack_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["cognito-identity:*", "iot:*"], resources=["*"] ) common_stack.add_to_common_role_policies(self, policy_to_add=stack_policy)
def principal_for(self, identity_pool: cognito.CfnIdentityPool, state: str) -> iam.FederatedPrincipal: return iam.FederatedPrincipal( federated="cognito-identity.amazonaws.com", assume_role_action="sts:AssumeRoleWithWebIdentity", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": state }, }, )
def _build_cognito(self, *, stack): self.cognito_identity_pool = cognito.CfnIdentityPool( stack, 'identitypool', allow_unauthenticated_identities=True) self.unauthenticatedRole = iam.Role( stack, 'CognitoDefaultUnauthenticatedRole', assumed_by=iam.FederatedPrincipal( 'cognito-identity.amazonaws.com', { "StringEquals": { "cognito-identity.amazonaws.com:aud": self.cognito_identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, "sts:AssumeRoleWithWebIdentity")) self.unauthenticatedRole.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "mobiletargeting:PutEvents", "mobiletargeting:GetUserEndpoints", "mobiletargeting:UpdateEndpoint" ], resources=[self.pinpoint_instance.attr_arn + "/*"])) self.unauthenticatedRole.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "mobileanalytics:PutEvents", # TODO : least privileged "cognito-sync:*" ], resources=["*"])) self.default_policy_cognito_identity_pool = cognito.CfnIdentityPoolRoleAttachment( stack, 'identitypoolrolatt', identity_pool_id=self.cognito_identity_pool.ref, roles={ 'unauthenticated': self.unauthenticatedRole.role_arn, # If you need authenticated users, you need to have a separate role # Here, both have the same 'authenticated': self.unauthenticatedRole.role_arn })
def get_default_role_for_identity_pool( scope: core.Construct, identity_pool_id: str, role_resource_id_prefix: str, auth: bool = True, kwargs={}, ) -> aws_iam.Role: role_policy = get_default_role_policy(auth=auth, kwargs=kwargs) role_condition = get_default_role_condition(identity_pool_id=identity_pool_id, auth=auth) role = aws_iam.Role( scope, role_resource_id_prefix + ("auth" if auth else "unauth"), assumed_by=aws_iam.FederatedPrincipal( "cognito-identity.amazonaws.com", role_condition, "sts:AssumeRoleWithWebIdentity" ), ) role.add_to_policy(role_policy) return role
def __init__(f, scope: Construct, construct_id: str, eks_cluster, env, **kwargs) -> None: super().__init__(scope, construct_id, env=env, **kwargs) oidc_url = eks_cluster.cluster_open_id_connect_issuer_url iam_oic = iam.OpenIdConnectProvider(f, construct_id, url=oidc_url, client_ids=['sts.amazonaws.com']) oidc_arn = iam_oic.open_id_connect_provider_arn oidc_provider = re.sub("https://", "", oidc_url) def string_like(name_space, sa_name): string_like = CfnJson( f, f'JsonCondition{sa_name}', value={ f'{oidc_provider}:sub': f'system:serviceaccount:{name_space}:{sa_name}', f'{oidc_provider}:aud': 'sts.amazonaws.com' }) return string_like alb_controller_role = iam.Role( f, 'AlbControllerRole', role_name='eks-aws-load-balancer-controller-sa', assumed_by=iam.FederatedPrincipal( federated=f'{iam_oic.open_id_connect_provider_arn}', conditions={ 'StringEquals': string_like('kube-system', 'aws-load-balancer-controller') }, assume_role_action='sts:AssumeRoleWithWebIdentity')) alb_controller_role.add_managed_policy( iam.ManagedPolicy.from_managed_policy_name( f, "EksAWSLoadBalancerController", managed_policy_name='EksAWSLoadBalancerController')) alb_controller_role.add_managed_policy( iam.ManagedPolicy.from_managed_policy_name( f, "EksAWSLoadBalancerControllerAdditional", managed_policy_name='EksAWSLoadBalancerControllerAdditional'))
def base_federated_role( construct, resource_name: str, federated_resource: str, assume_role_action: str, conditions: dict, actions: list, resources: list, ): """ Function that generates an IAM Federated Role with a Policy. :param construct: Custom construct that will use this function. From the external construct is usually 'self'. :param resource_name: Name of the resource. Used for naming purposes. :param federated_resource: Resource used to define a Federated Principal. Has to match an AWS Resource. For example, 'cognito-identity' -> 'cognito-identity.amazonaws.com'. :param assume_role_action: The conditions under which the policy is in effect. :param conditions: Generally an STS Action. :param actions: Action list containing AWS IAM defined actions. For example 'sns:Publish' :param resources: List of resources ARNs defined by AWS. :return: IAM Federated Role with an IAM Policy attached. """ try: # Defining IAM Role # Defining Service Principal iam_role_name = construct.prefix + "_role_" + resource_name + "_" + construct.environment_ iam_policy_name = construct.prefix + "_policy_" + resource_name + "_" + construct.environment_ principal = iam.FederatedPrincipal( federated=f"{federated_resource}.amazonaws.com", conditions=conditions, assume_role_action=assume_role_action, ) # Defining IAM Role role = iam.Role(construct, id=iam_role_name, role_name=iam_role_name, assumed_by=principal) # Defining Policy Statement, Policy and Attaching to Role policy_statements = iam.PolicyStatement(actions=actions, resources=resources) policy = iam.Policy(construct, id=iam_policy_name, policy_name=iam_policy_name, statements=[policy_statements]) policy.attach_to_role(role=role) except Exception: print(traceback.format_exc()) else: return role
def create_wic_provider_test_role(self) -> None: wic_provider_test_role_condition = { "StringEquals": {"graph.facebook.com:app_id": self._facebook_app_id} } wic_provider_test_role = aws_iam.Role( self, "wic_provider_test_role", assumed_by=aws_iam.FederatedPrincipal( "graph.facebook.com", wic_provider_test_role_condition, "sts:AssumeRoleWithWebIdentity", ), ) wic_provider_test_role.add_to_policy( aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=["translate:TranslateText"], resources=["*"] ) ) self.parameters_to_save["WICProviderTestRoleArn"] = wic_provider_test_role.role_arn
def __init__( # pylint: disable=too-many-arguments,too-many-locals self, scope: Construct, stack_id: str, *, botocore_lambda_layer: aws_lambda_python.PythonLayerVersion, datasets_table: Table, deploy_env: str, state_machine: aws_stepfunctions.StateMachine, state_machine_parameter: aws_ssm.StringParameter, storage_bucket: aws_s3.Bucket, storage_bucket_parameter: aws_ssm.StringParameter, validation_results_table: Table, **kwargs: Any, ) -> None: super().__init__(scope, stack_id, **kwargs) if saml_provider_arn := environ.get("DATALAKE_SAML_IDENTITY_PROVIDER_ARN"): principal = aws_iam.FederatedPrincipal( federated=saml_provider_arn, assume_role_action="sts:AssumeRoleWithSAML", conditions={"StringEquals": {"SAML:aud": "https://signin.aws.amazon.com/saml"}}, )
def __init__(self, scope: core.Construct, id: str, federated: str, *, description=None, external_ids=None, inline_policies=None, managed_policies=None, max_session_duration=None, path=None, permissions_boundary=None) -> None: """Same as CDK IAM Role above. The assumed_by is a FederatedPrincipal 'federated' (your IAM Idp arn). Args: federated: ARN of the AWS IAM Idp through which users assume this IAM Role """ assumed_by = iam.FederatedPrincipal( federated=federated, conditions={ 'StringEquals': { 'SAML:aud': 'https://signin.aws.amazon.com/saml' } }, assume_role_action='sts:AssumeRoleWithSAML') super().__init__(scope=scope, id=id, assumed_by=assumed_by, description=description, external_ids=external_ids, inline_policies=inline_policies, managed_policies=managed_policies, max_session_duration=max_session_duration, path=path, permissions_boundary=permissions_boundary)
def setup_identity_pool(self): identity_pool = aws_cognito.CfnIdentityPool( self, "pinpoint_integ_test_android", allow_unauthenticated_identities=True) unauthenticated_role = aws_iam.Role( self, "CognitoDefaultUnauthenticatedRole", assumed_by=aws_iam.FederatedPrincipal( "cognito-identity.amazonaws.com", { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, "sts:AssumeRoleWithWebIdentity", ), ) unauthenticated_role.add_to_policy( aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, actions=["cognito-sync:*", "iot:*"], resources=["*"], )) aws_cognito.CfnIdentityPoolRoleAttachment( self, "DefaultValid", identity_pool_id=identity_pool.ref, roles={"unauthenticated": unauthenticated_role.role_arn}, ) self._parameters_to_save["identity_pool_id"] = identity_pool.ref
def _create_identity_pool(self) -> cognito.CfnIdentityPool: provider_name = ( self.user_pool.user_pool_provider_name if hasattr(self.user_pool, "user_pool_provider_name") else f"cognito-idp.{self.context.region}.amazonaws.com/{self.context.user_pool_id}" ) pool = cognito.CfnIdentityPool( scope=self, id="identity-pool", identity_pool_name=self.id.replace("-", "_"), allow_unauthenticated_identities=False, allow_classic_flow=False, cognito_identity_providers=[ cognito.CfnIdentityPool.CognitoIdentityProviderProperty( provider_name=provider_name, client_id=self.user_pool_client.user_pool_client_id, ) ], ) name = f"{self.id}-cognito-authenticated-identity-role" authenticated_role = iam.Role( scope=self, id=name, role_name=name, assumed_by=iam.FederatedPrincipal( federated="cognito-identity.amazonaws.com", conditions={ "StringEquals": {"cognito-identity.amazonaws.com:aud": pool.ref}, "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"}, }, assume_role_action="sts:AssumeRoleWithWebIdentity", ), inline_policies={ "cognito-default": iam.PolicyDocument( statements=[ iam.PolicyStatement( actions=[ "mobileanalytics:PutEvents", "cognito-sync:*", "cognito-identity:*", ], resources=["*"], ) ] ), "team-context-parameter": iam.PolicyDocument( statements=[ iam.PolicyStatement( actions=["ssm:DescribeParameters", "ssm:GetParameters"], resources=[ f"arn:aws:ssm:{self.context.region}:{self.context.account_id}:" f"parameter/orbit/{self.context.name}/teams/*" ], ) ] ), }, ) name = f"{self.id}-cognito-unauthenticated-identity-role" unauthenticated_role = iam.Role( scope=self, id=name, role_name=name, assumed_by=iam.FederatedPrincipal( federated="cognito-identity.amazonaws.com", conditions={ "StringEquals": {"cognito-identity.amazonaws.com:aud": pool.ref}, "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "unauthenticated"}, }, assume_role_action="sts:AssumeRoleWithWebIdentity", ), inline_policies={ "cognito-default": iam.PolicyDocument( statements=[ iam.PolicyStatement( actions=[ "mobileanalytics:PutEvents", "cognito-sync:*", ], resources=["*"], ) ] ) }, ) cognito.CfnIdentityPoolRoleAttachment( scope=self, id=f"{self.id}-role-attachment", identity_pool_id=pool.ref, roles={ "authenticated": authenticated_role.role_arn, "unauthenticated": unauthenticated_role.role_arn, }, ) return pool
def __init__(self, scope: Construct, construct_id: str, env, vpc, **kwargs) -> None: super().__init__(scope, construct_id, env=env, **kwargs) efs_sg = ec2.SecurityGroup(self, 'EfsSG', vpc=vpc, description='EKS EFS SG', security_group_name='eks-efs') efs_sg.add_ingress_rule(ec2.Peer.ipv4('10.3.0.0/16'), ec2.Port.all_traffic(), "EFS VPC access") Tags.of(efs_sg).add(key='cfn.eks-dev.stack', value='sg-stack') Tags.of(efs_sg).add(key='Name', value='eks-efs') Tags.of(efs_sg).add(key='env', value='dev') file_system = efs.FileSystem( self, construct_id, vpc=vpc, file_system_name='eks-efs', lifecycle_policy=efs.LifecyclePolicy.AFTER_14_DAYS, removal_policy=RemovalPolicy.DESTROY, security_group=efs_sg) Tags.of(file_system).add(key='cfn.eks-dev.stack', value='efs-stack') Tags.of(file_system).add(key='efs.csi.aws.com/cluster', value='true') Tags.of(file_system).add(key='Name', value='eks-efs') Tags.of(file_system).add(key='env', value='dev') policy_statement_1 = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "elasticfilesystem:DescribeAccessPoints", "elasticfilesystem:DescribeFileSystems" ], resources=['*'], conditions={ 'StringEquals': { "aws:RequestedRegion": "ap-northeast-2" } }) policy_statement_2 = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "elasticfilesystem:CreateAccessPoint", "elasticfilesystem:DeleteAccessPoint" ], resources=['*'], conditions={ 'StringEquals': { "aws:ResourceTag/efs.csi.aws.com/cluster": "true" } }) # EFS CSI SA efs_csi_role = iam.Role( self, 'EfsCSIRole', role_name='eks-efs-csi-sa', assumed_by=iam.FederatedPrincipal( federated=oidc_arn, assume_role_action='sts:AssumeRoleWithWebIdentity', conditions={ 'StringEquals': string_like('kube-system', 'efs-csi-controller-sa') }, )) for stm in [policy_statement_1, policy_statement_2]: efs_csi_role.add_to_policy(stm) Tags.of(efs_csi_role).add(key='cfn.eks-dev.stack', value='role-stack')
def __init__(self, scope: core.Construct, id: str, common_stack: CommonStack, **kwargs) -> None: super().__init__(scope, id, **kwargs) self._supported_in_region = self.is_service_supported_in_region() app = pinpoint.CfnApp(self, "android-integ-test", name="android-integ-test") identity_pool = cognito.CfnIdentityPool( self, "pinpoint_integ_test_android", allow_unauthenticated_identities=True) unauthenticated_role = iam.Role( self, "CognitoDefaultUnauthenticatedRole", assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, "sts:AssumeRoleWithWebIdentity", ), ) unauthenticated_role.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["cognito-sync:*", "mobiletargeting:PutEvents"], resources=["*"], )) cognito.CfnIdentityPoolRoleAttachment( self, "DefaultValid", identity_pool_id=identity_pool.ref, roles={"unauthenticated": unauthenticated_role.role_arn}, ) self._parameters_to_save = { "identity_pool_id": identity_pool.ref, "AppId": app.ref, "Region": core.Aws.REGION, } self.save_parameters_in_parameter_store(platform=Platform.ANDROID) stack_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "mobileanalytics:PutEvents", "mobiletargeting:PutEvents", "mobiletargeting:UpdateEndpoint", ], resources=["*"], ) common_stack.add_to_common_role_policies(self, policy_to_add=stack_policy)
def build_team_role( scope: core.Construct, context: "Context", team_name: str, policy_names: List[str], scratch_bucket: s3.IBucket, team_kms_key: kms.Key, ) -> iam.Role: env_name = context.name partition = core.Aws.PARTITION account = core.Aws.ACCOUNT_ID region = core.Aws.REGION lake_role_name: str = f"orbit-{env_name}-{team_name}-role" kms_keys = [team_kms_key.key_arn] scratch_bucket_kms_key = IamBuilder.get_kms_key_scratch_bucket(context=context) if scratch_bucket_kms_key: kms_keys.append(scratch_bucket_kms_key) lake_operational_policy = iam.ManagedPolicy( scope=scope, id="lake_operational_policy", managed_policy_name=f"orbit-{env_name}-{team_name}-user-access", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "s3:*", ], resources=[ f"arn:{partition}:s3:::sagemaker-{region}-{account}", f"arn:{partition}:s3:::sagemaker-{region}-{account}/*", scratch_bucket.bucket_arn, f"{scratch_bucket.bucket_arn}/{team_name}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["s3:List*", "s3:Get*"], resources=[ f"arn:{partition}:s3:::{context.toolkit.s3_bucket}", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["ssm:Get*"], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit*", f"arn:{partition}:ssm:{region}:{account}:parameter/emr_launch/", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["ssm:PutParameter"], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/user*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "redshift:GetClusterCredentials", "redshift:CreateClusterUser", "redshift:DeleteCluster", ], resources=[ f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*", f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*/master", f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*/defaultdb", f"arn:{partition}:redshift:{region}:{account}:dbname:{env_name}-{team_name}*/defaultdb", f"arn:{partition}:redshift:{region}:{account}:cluster:{env_name}-{team_name}*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "sns:*", ], resources=[ f"arn:{partition}:sns:{region}:{account}:{env_name}-{team_name}*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["iam:PassRole"], resources=[f"arn:{partition}:iam::{account}:role/{lake_role_name}"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:Describe*", "codeartifact:Describe*", "codeartifact:Get*", "codeartifact:List*", "codeartifact:Read*", "sts:GetServiceBearerToken", "s3:ListAllMyBuckets", "lambda:List*", "lambda:Get*", "iam:List*", "tag:GetResources", "ecr:Get*", "ecr:List*", "ecr:Describe*", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability", "cloudwatch:PutMetricData", "redshift:DescribeClusters", "states:List*", "states:Get*", "states:Describe*", "glue:Get*", "glue:List*", "glue:Search*", "athena:*", "ecs:Describe*", "ecs:ListTasks", "ec2:Describe*", "redshift:DescribeClusters", "elasticmapreduce:List*", "elasticmapreduce:Get*", "elasticmapreduce:Describe*", "elasticmapreduce:TerminateJobFlows", "elasticmapreduce:AddJobFlowSteps", "sagemaker:List*", "sagemaker:Get*", "sagemaker:Describe*", "sagemaker:CreateModel", "sagemaker:DeleteModelPackage", "sagemaker:UpdateEndpointWeightsAndCapacities", "sagemaker:DeleteAlgorithm", "sagemaker:Search", "sagemaker:UpdateWorkteam", "sagemaker:DeleteModel", "sagemaker:CreateModelPackage", "sagemaker:DeleteWorkteam", "sagemaker:CreateEndpoint", "sagemaker:CreateEndpointConfig", "sagemaker:RenderUiTemplate", "sagemaker:StopTransformJob", "sagemaker:CreateLabelingJob", "sagemaker:DeleteEndpointConfig", "sagemaker:CreateAlgorithm", "sagemaker:CreateTrainingJob", "sagemaker:StopHyperParameterTuningJob", "sagemaker:DeleteEndpoint", "sagemaker:CreateTransformJob", "sagemaker:InvokeEndpoint", "sagemaker:CreateWorkteam", "sagemaker:StopLabelingJob", "sagemaker:UpdateEndpoint", "sagemaker:CreateCompilationJob", "sagemaker:StopCompilationJob", "sagemaker:CreateHyperParameterTuningJob", "lakeformation:GetDataAccess", ], resources=["*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "logs:List*", "logs:Describe*", "logs:StartQuery", "logs:StopQuery", "logs:Get*", "logs:Filter*", "events:*", ], resources=[ f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*:log-stream:*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws/eks/orbit*", f"arn:{partition}:events:{region}:{account}:rule/orbit-{env_name}-{team_name}-*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ecr:*", ], resources=[f"arn:{partition}:ecr:{region}:{account}:repository/orbit-{env_name}-users-*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt", "kms:GenerateDataKey", "kms:DescribeKey"], resources=kms_keys, ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "lambda:InvokeFunction", ], resources=[ f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-{team_name}-*", f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-token-validation", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "cloudformation:DescribeStacks", ], resources=[ f"arn:{partition}:cloudformation:{region}:{account}:stack/orbit-{env_name}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:GetParameters", "ssm:DescribeParameters", "ssm:GetParameter", "ssm:DescribeParameter", ], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:DeleteParameter", "ssm:DeleteParameters", ], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/changeset", f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/manifest", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:DescribeParameters", ], resources=[f"arn:{partition}:ssm:{region}:{account}:*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["s3:Put*"], resources=[ f"arn:{partition}:s3:::{context.toolkit.s3_bucket}", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/cli/remote/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["codebuild:StartBuild", "codebuild:BatchGetBuilds"], resources=[f"arn:{partition}:codebuild:{region}:{account}:project/orbit-{env_name}"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:DescribeLogStreams", "logs:PutLogEvents", ], resources=[ f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}:log-stream:*" # noqa ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "logs:List*", "logs:Describe*", "logs:StartQuery", "logs:StopQuery", "logs:Get*", "logs:Filter*", "events:*", ], resources=[ f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}*:log-stream:*", # noqa ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ecr:InitiateLayerUpload", ], resources=[ f"arn:{partition}:ecr:{region}:{account}:repository/*", ], ), ], ) managed_policies = [ lake_operational_policy, # For EKS iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="AmazonEKS_CNI_Policy"), ] # Parse list to IAM policies aws_managed_user_policies = [ iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name=policy_name) for policy_name in policy_names if "orbit" not in policy_name ] orbit_custom_policies = [ iam.ManagedPolicy.from_managed_policy_name(scope=scope, id=policy_name, managed_policy_name=policy_name) for policy_name in policy_names if "orbit" in policy_name ] managed_policies = managed_policies + aws_managed_user_policies + orbit_custom_policies role = iam.Role( scope=scope, id=lake_role_name, role_name=lake_role_name, assumed_by=iam.CompositePrincipal( iam.ServicePrincipal("ec2.amazonaws.com"), iam.ServicePrincipal("glue.amazonaws.com"), iam.ServicePrincipal("sagemaker.amazonaws.com"), iam.ServicePrincipal("redshift.amazonaws.com"), iam.ServicePrincipal("codepipeline.amazonaws.com"), iam.ServicePrincipal("personalize.amazonaws.com"), ), managed_policies=managed_policies, ) if role.assume_role_policy: role.assume_role_policy.add_statements( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["sts:AssumeRoleWithWebIdentity"], principals=[ iam.FederatedPrincipal( federated=f"arn:{partition}:iam::{account}:oidc-provider/{context.eks_oidc_provider}", conditions={ "StringLike": { f"{context.eks_oidc_provider}:sub": f"system:serviceaccount:{team_name}:*" } }, ) ], ), ) return role
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) aws_region = os.environ.get("CDK_DEPLOY_REGION", os.environ["CDK_DEFAULT_REGION"]) account_id = os.environ.get("CDK_DEPLOY_ACCOUNT", os.environ["CDK_DEFAULT_ACCOUNT"]) ssm_client = boto3.client('ssm', aws_region) # Prepare pipeline config details in SSM parameters if prefix == 'us': self.qs_reports_env_config = {"Permissions": [{"Group_Name": "critical", "Reports": ["Sales Results - Critical"], "ns_name": "default"}, {"Group_Name": "highlyconfidential", "Reports": ["Field Operations Dashboard", "Sales Results - Highly Confidential" ], "ns_name": "default"}, {"Group_Name": "bi-developer", "Reports": ["all"], "ns_name": "default"}, {"Group_Name": "bi-admin", "Reports": ["all"], "ns_name": "default"}, {"Group_Name": "power-reader", "Reports": ["read-all"], "ns_name": "default"}, {"Group_Name": "3rd-party", "Reports": ["Marketing KPIs"], "ns_name": "3rd-party"} ] } if prefix == 'eu': self.qs_reports_env_config = {"Permissions": [{"Group_Name": "eu-critical", "Reports": ["EUResults - Critical"]}, {"Group_Name": "bi-developer", "Reports": ["all"]}, {"Group_Name": "bi-admin", "Reports": ["all"]}, {"Group_Name": "eu-highlyconfidential", "Reports": ["EUField Operations Dashboard", "EUResults - Highly Confidential"]}, {"Group_Name": "power-reader", "Reports": ["read-all"]}]} self.qs_reports_env_config_ssm = ssm.StringParameter( self, '/qs/config/access', string_value=json.dumps(self.qs_reports_env_config), parameter_name='/qs/config/access' ) #group-user mapping information is stored in s3 bucket. A ssm parameter stores the bucket name. self.qs_user_group_config = {'bucket-name':f'qs-granular-access-demo-{account_id}'} self.qs_user_group_config_ssm = ssm.StringParameter( self, '/qs/config/groups', string_value=json.dumps(self.qs_user_group_config), parameter_name='/qs/config/groups' ) # group-role mapping information is stored in a ssm parameter. self.qs_role_config = {'default_bi-developer': 'AUTHOR', 'default_bi-admin': 'ADMIN', 'default_power-reader': 'AUTHOR', 'default_critical': 'READER', 'default_highlyconfidential': 'READER', 'default_marketing': 'AUTHOR', '3rd-party_3rd-party': 'AUTHOR' } self.qs_role_config_ssm = ssm.StringParameter( self, '/qs/config/roles', string_value=json.dumps(self.qs_role_config), parameter_name='/qs/config/roles' ) # group-namespace mapping information is stored in a ssm parameter. self.qs_ns_config = {"ns":['default', '3rd-party']} self.qs_ns_config_ssm = ssm.StringParameter( self, '/qs/config/ns', string_value=json.dumps(self.qs_ns_config), parameter_name='/qs/config/ns' ) lambda_role = iam.Role( self, id='lambda-role', description='Role for the quicksight lambda', role_name=f'{aws_region}-role-quicksight-lambda', max_session_duration=core.Duration.seconds(3600), assumed_by=iam.ServicePrincipal('lambda.amazonaws.com'), inline_policies={ 'AllowS3Access': iam.PolicyDocument( statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["kms:GetParametersForImport", "kms:GetPublicKey", "kms:ListKeyPolicies", "kms:ListRetirableGrants", "kms:GetKeyPolicy", "kms:ListResourceTags", "kms:ListGrants", "kms:GetParametersForImport", "kms:GetKeyRotationStatus", "kms:DescribeKey", "kms:CreateGrant", "kms:ListAliases", "kms:ListKeys", "kms:DescribeCustomKeyStores", "ssm:GetParameters", "ssm:GetParameter", "ssm:GetParametersByPath" ], resources=['*'] ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["lambda:InvokeFunction", "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents", "quicksight:*", "s3:HeadBucket", "s3:ListAllMyBuckets", "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:GetObjectVersionForReplication", "s3:GetBucketPolicy", "s3:GetObjectVersion", "cloudwatch:PutMetricData", "sts:GetCallerIdentity"], resources=['*'] ) ] ) } ) user_init = _lambda.Function(self, 'user_init', handler='user_init.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset(os.path.join(current_dir, '../lambda_functions/user_init/')), function_name='user_init', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=512 ) check_team_members = _lambda.Function(self, 'check_team_members', handler='check_team_members.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset(os.path.join(current_dir, '../lambda_functions/check_team_members/')), function_name='check_team_members', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=512, environment={'aws_region': f'{core.Aws.REGION}'} ) downgrade_user = _lambda.Function(self, 'downgrade_user', handler='downgrade_user.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.from_asset(os.path.join(current_dir, '../lambda_functions/downgrade_user/')), function_name='downgrade_user', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=2048, environment={'aws_region': f'{core.Aws.REGION}'} ) granular_user_govenance = _lambda.Function(self, 'granular_user_govenance', handler='granular_user_govenance.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset(os.path.join(current_dir, '../lambda_functions/granular_user_govenance')), function_name='granular_user_govenance', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=2048, environment={'aws_region': f'{core.Aws.REGION}'} ) granular_access_assets_govenance = _lambda.Function(self, 'granular_access_assets_govenance', handler='granular_access_assets_govenance.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset(os.path.join(current_dir, '../lambda_functions/granular_access_assets_govenance')), function_name='granular_access_assets_govenance', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=2048, environment={'aws_region': f'{core.Aws.REGION}'} ) quicksight_event_rule = events.Rule(self, 'QuickSightCWEventRule', description='CloudWatch rule to detect new QuickSight user creation', rule_name='qs-gc-user-creation', targets=[targets.LambdaFunction(user_init)], event_pattern=events.EventPattern(source=['aws.quicksight'], detail_type=[ 'AWS Service Event via CloudTrail'], detail={ "eventSource": [ "quicksight.amazonaws.com"], "eventName": ["CreateUser"] } ) ) quicksight_schedule_rule = events.Rule(self, 'quicksight_schedule_rule', description='CloudWatch rule to run QS objects/groups assignment every hour', rule_name='qs-gc-every-hour', schedule=events.Schedule.cron(minute="0"), targets=[targets.LambdaFunction(granular_user_govenance)] ) quicksight_assume_condition_object = {"StringEquals": { "SAML:aud": "https://signin.aws.amazon.com/saml"}} quicksight_federated_prin_with_conditionb_obj = iam.FederatedPrincipal( f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:saml-provider/saml', quicksight_assume_condition_object, 'sts:AssumeRoleWithSAML') quicksight_resource_scope = '${aws:userid}' quicksight_reader_saml_inline_policies = { 'AllowQuicksightAccessSAML': iam.PolicyDocument( statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['quicksight:CreateReader'], resources=[ f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}'] ) ] ) } quicksight_users = iam.Role( self, id=f"quicksight-fed-{prefix}-users", # this is the default group with no access description='Role for the quicksight reader SAML', role_name=f"quicksight-fed-{prefix}-users", max_session_duration=core.Duration.seconds(3600), assumed_by=quicksight_federated_prin_with_conditionb_obj, inline_policies=quicksight_reader_saml_inline_policies )
def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None: super().__init__(scope, id, **kwargs) power_transformers = aws_dynamodb.Table( self, "PowerTransformers", table_name="PowerTransformers", partition_key=aws_dynamodb.Attribute( name="name", type=aws_dynamodb.AttributeType.STRING), removal_policy=core.RemovalPolicy.DESTROY) function = _lambda.Function( self, "power_transformers_data_enrichment", function_name="power_transformers_data_enrichment", runtime=_lambda.Runtime.PYTHON_3_7, handler="lambda_function.handler", code=_lambda.Code.asset("./lambda/data-enrichment")) function.add_environment('TABLE_NAME', power_transformers.table_name) function.add_to_role_policy( iam.PolicyStatement(actions=['dynamodb:GetItem'], resources=[f"{power_transformers.table_arn}"], effect=iam.Effect.ALLOW)) function.add_permission( principal=iam.ServicePrincipal('iotanalytics.amazonaws.com'), action='lambda:InvokeFunction', id='pt-iot-analytics') bucket = s3.Bucket( self, 'PowerTransformersTelemetryBucket', bucket_name=f"{props['projectName'].lower()}-{core.Aws.ACCOUNT_ID}", removal_policy=core.RemovalPolicy.DESTROY) output_bucket = s3.Bucket( self, 'PowerTransformersProcessedDataBucket', bucket_name= f"{props['projectName'].lower()}-output-{core.Aws.ACCOUNT_ID}", removal_policy=core.RemovalPolicy.DESTROY) # Apply least privilege s3_role = iam.Role( self, "IotAnalyticsS3Role", assumed_by=iam.ServicePrincipal("iotanalytics.amazonaws.com"), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( 'AmazonS3FullAccess') ]) # s3_role.add_to_policy(iam.PolicyStatement(actions=["s3:PutObject", "s3:DeleteObject", "s3:GetBucketLocation"], # resources=[f"{bucket.bucket_arn}", f"{bucket.bucket_arn}/*"], effect=iam.Effect.ALLOW)) # Apply least privilege s3_output_role = iam.Role( self, "IotAnalyticsS3OutputRole", assumed_by=iam.ServicePrincipal("iotanalytics.amazonaws.com"), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( 'AmazonS3FullAccess') ], ) # s3_output_role.add_to_policy(iam.PolicyStatement(actions=["s3:PutObject", "s3:DeleteObject", "s3:GetBucketLocation"], # resources=[f"{output_bucket.bucket_arn}", f"{output_bucket.bucket_arn}/*"], effect=iam.Effect.ALLOW)) project_name = props['projectName'].lower().replace('-', '_') channel_name = f"{project_name}_channel" datastore_name = f"{project_name}_datastore" channel_s3 = CHANNEL.CustomerManagedS3Property( bucket=bucket.bucket_name, key_prefix='raw/', role_arn=s3_role.role_arn) channel_storage = CHANNEL.ChannelStorageProperty( customer_managed_s3=channel_s3) CHANNEL(self, 'iot_channel', channel_name=channel_name, channel_storage=channel_storage) datastore_s3 = DATASTORE.CustomerManagedS3Property( bucket=bucket.bucket_name, key_prefix='processed/', role_arn=s3_role.role_arn) datastore_storage = DATASTORE.DatastoreStorageProperty( customer_managed_s3=datastore_s3) datastore = DATASTORE(self, 'iot_datastore', datastore_name=datastore_name, datastore_storage=datastore_storage) channel_activity = PIPELINE.ChannelProperty(name='ChannelActivity', channel_name=channel_name, next='LambdaActivity') lambda_activity = PIPELINE.LambdaProperty( name='LambdaActivity', lambda_name='power_transformers_data_enrichment', next='DatastoreActivity', batch_size=10) datastore_activity = PIPELINE.DatastoreProperty( name='DatastoreActivity', datastore_name=datastore_name) pipeline_activities = PIPELINE.ActivityProperty( channel=channel_activity, lambda_=lambda_activity, datastore=datastore_activity) pipeline = PIPELINE(self, 'iot_pipeline', pipeline_name=f"{project_name}_pipeline", pipeline_activities=[pipeline_activities]) pipeline.add_depends_on(datastore) query_action = DATASET.QueryActionProperty( sql_query=f"SELECT * FROM {datastore_name}") action = DATASET.ActionProperty(query_action=query_action, action_name='sqlAction') schedule_expression = DATASET.ScheduleProperty( schedule_expression='cron(1/5 * * * ? *)') trigger_schedule = DATASET.TriggerProperty( schedule=schedule_expression) dataset_s3_destination = DATASET.S3DestinationConfigurationProperty( bucket=output_bucket.bucket_name, key= 'dataset/Version/!{iotanalytics:scheduleTime}_!{iotanalytics:versionId}.csv', role_arn=s3_output_role.role_arn) dataset_destination = DATASET.DatasetContentDeliveryRuleDestinationProperty( s3_destination_configuration=dataset_s3_destination) content_delivery_rules = DATASET.DatasetContentDeliveryRuleProperty( destination=dataset_destination) dataset = DATASET(self, 'iot_dataset', dataset_name=f"{project_name}_dataset", actions=[action], triggers=[trigger_schedule], content_delivery_rules=[content_delivery_rules]) dataset.add_depends_on(datastore) user_pool = aws_cognito.UserPool( self, 'kibanaUserPool', self_sign_up_enabled=False, sign_in_aliases=aws_cognito.SignInAliases(username=True, email=True)) aws_cognito.CfnUserPoolDomain( self, 'userPoolDomain', user_pool_id=user_pool.user_pool_id, domain= f"{props['projectName'].lower()}-{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}" ) user_pool_client = aws_cognito.UserPoolClient(self, 'kibanaClientId', user_pool=user_pool, generate_secret=True) identity_provider = aws_cognito.CfnIdentityPool.CognitoIdentityProviderProperty( client_id=user_pool_client.user_pool_client_id, provider_name=user_pool.user_pool_provider_name) identity_pool = aws_cognito.CfnIdentityPool( self, 'identityPool', allow_unauthenticated_identities=False, cognito_identity_providers=[identity_provider]) # Apply least privilege cognito_authenticated_role = iam.Role( self, "CognitoAuthRole", assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", assume_role_action='sts:AssumeRoleWithWebIdentity', conditions={ 'StringEquals': { 'cognito-identity.amazonaws.com:aud': identity_pool.ref }, 'ForAnyValue:StringLike': { 'cognito-identity.amazonaws.com:amr': 'authenticated' } }), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( 'AmazonESFullAccess') ]) aws_cognito.CfnIdentityPoolRoleAttachment( self, 'identityPoolRoleAttachment', identity_pool_id=identity_pool.ref, roles={'authenticated': cognito_authenticated_role.role_arn}) cognito_options = DOMAIN.CognitoOptionsProperty( enabled=True, user_pool_id=user_pool.user_pool_id, identity_pool_id=identity_pool.ref, role_arn= f"arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/service-role/CognitoAccessForAmazonES" ) ebs_options = DOMAIN.EBSOptionsProperty(ebs_enabled=True, volume_size=10, volume_type='gp2') elasticsearch_cluster_config = DOMAIN.ElasticsearchClusterConfigProperty( instance_count=1, instance_type='r5.large.elasticsearch') encryption_at_rest_options = DOMAIN.EncryptionAtRestOptionsProperty( enabled=True) node_to_node_encryption_options = DOMAIN.NodeToNodeEncryptionOptionsProperty( enabled=True) snapshot_options = DOMAIN.SnapshotOptionsProperty( automated_snapshot_start_hour=0) es_domain_arn = f"arn:aws:es:{core.Aws.REGION}:{core.Aws.ACCOUNT_ID}:domain/{props['projectName'].lower()}/*" es_policy_statement = iam.PolicyStatement(actions=['es:*'], resources=[es_domain_arn]) es_policy_statement.add_arn_principal( cognito_authenticated_role.role_arn) policy_document = iam.PolicyDocument() policy_document.add_statements(es_policy_statement) domain = DOMAIN( self, 'elasticsearch', domain_name=f"{props['projectName'].lower()}", cognito_options=cognito_options, ebs_options=ebs_options, elasticsearch_cluster_config=elasticsearch_cluster_config, encryption_at_rest_options=encryption_at_rest_options, node_to_node_encryption_options=node_to_node_encryption_options, snapshot_options=snapshot_options, elasticsearch_version='6.8', access_policies=policy_document) function = _lambda.Function( self, "load_data_from_s3_to_es", function_name="load_data_from_s3_to_es", runtime=_lambda.Runtime.PYTHON_3_7, handler="lambda_function.handler", code=_lambda.Code.asset("./lambda/load-data-from-s3-to-es.zip")) function.add_environment('ES_HOST', domain.attr_domain_endpoint) function.add_environment('ES_REGION', f"{core.Aws.REGION}") function.add_to_role_policy( iam.PolicyStatement(actions=['es:ESHttpPost'], resources=[es_domain_arn], effect=iam.Effect.ALLOW)) function.add_to_role_policy( iam.PolicyStatement(actions=['s3:GetObject'], resources=[f"{output_bucket.bucket_arn}/*"], effect=iam.Effect.ALLOW)) notification = aws_s3_notifications.LambdaDestination(function) output_bucket.add_event_notification(s3.EventType.OBJECT_CREATED, notification) load_ddb_custom_resource = LoadDDBDataCustomResource( self, "LoadDDBData", table_name=power_transformers.table_name, table_arn=power_transformers.table_arn) load_ddb_custom_resource.node.add_dependency(power_transformers) load_es_index_custom_resource = LoadESIndexCustomResource( self, "LoadESIndex", es_host=domain.attr_domain_endpoint, es_region=f"{core.Aws.REGION}", es_domain_arn=es_domain_arn) load_es_index_custom_resource.node.add_dependency(domain) load_kibana_dashboards_custom_resource = LoadKibanaDashboardsCustomResource( self, "LoadKibanaDashboards", es_host=domain.attr_domain_endpoint, es_region=f"{core.Aws.REGION}", es_domain_arn=es_domain_arn) load_kibana_dashboards_custom_resource.node.add_dependency( load_es_index_custom_resource)
def add_cognito(self): password_policy = _cognito.PasswordPolicy( require_lowercase=False, require_digits=False, require_symbols=False, require_uppercase=False, ) user_pool = _cognito.UserPool( self, 'UserPool', password_policy=password_policy, user_pool_name='UserPool', self_sign_up_enabled=True, user_verification={ "email_subject": "Verify your email for our awesome app!", "email_body": "Hello {username}, Thanks for signing up to our awesome app! Your verification code is {####}", "email_style": _cognito.VerificationEmailStyle.CODE, }, user_invitation={ "email_subject": "Invite to join our awesome app!", "email_body": "Hello {username}, you have been invited to join our awesome app! Your temporary password is {####}", }, sign_in_aliases={"email": True}, auto_verify={"email": True}, ) user_pool_client = user_pool.add_client("AppClient", auth_flows={ "user_password": True, "user_srp": True, "refresh_token": True, "admin_user_password": True }) idp = _cognito.CfnIdentityPool.CognitoIdentityProviderProperty( client_id=user_pool_client.user_pool_client_id, provider_name=user_pool.user_pool_provider_name) identity_pool = _cognito.CfnIdentityPool( self, "IdPool", allow_unauthenticated_identities=False, cognito_identity_providers=[idp]) authenticated_principal = _iam.FederatedPrincipal( 'cognito-identity.amazonaws.com', { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" }, }, "sts:AssumeRoleWithWebIdentity") unauthenticated_principal = _iam.FederatedPrincipal( 'cognito-identity.amazonaws.com', { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" }, }, "sts:AssumeRoleWithWebIdentity") authenticated_role = _iam.Role(self, "CognitoDefaultAuthenticatedRole", assumed_by=authenticated_principal) unauthenticated_role = _iam.Role(self, "CognitoDefaultUnAuthenticatedRole", assumed_by=unauthenticated_principal) authenticated_policy = _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=[ "mobileanalytics:PutEvents", "cognito-sync:*", "cognito-identity:*" ], resources=["*"]) unauthenticated_policy = _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=[ "mobileanalytics:PutEvents", "cognito-sync:*", ], resources=["*"]) authenticated_role.add_to_policy(authenticated_policy) unauthenticated_role.add_to_policy(unauthenticated_policy) _cognito.CfnIdentityPoolRoleAttachment(self, "DefaultValidRoleAttachment", identity_pool_id=identity_pool.ref, roles={ "authenticated": authenticated_role.role_arn, "unauthenticated": unauthenticated_role.role_arn }) return user_pool, identity_pool, user_pool_client
def __init__(self, scope: core.Construct, id: str, common_stack: CommonStack, **kwargs) -> None: super().__init__(scope, id, **kwargs) self._supported_in_region = self.are_services_supported_in_region( ["cognito-identity", "kinesis"]) identity_pool = cognito.CfnIdentityPool( self, "pinpoint_integ_test_android", allow_unauthenticated_identities=True) unauthenticated_role = iam.Role( self, "CognitoDefaultUnauthenticatedRole", assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, "sts:AssumeRoleWithWebIdentity", ), ) unauthenticated_role.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["cognito-sync:*", "kinesis:*", "firehose:*"], resources=["*"], )) cognito.CfnIdentityPoolRoleAttachment( self, "DefaultValid", identity_pool_id=identity_pool.ref, roles={"unauthenticated": unauthenticated_role.role_arn}, ) firehose_s3_role = iam.Role( self, "FirehoseS3Role", assumed_by=iam.ServicePrincipal("firehose.amazonaws.com")) firehose_s3_role.add_to_policy( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject", ], resources=["*"], )) ingest_bucket = s3.Bucket(self, "test-aws-android-sdk-firehose-bucket") s3_dest_config = firehose.CfnDeliveryStream.S3DestinationConfigurationProperty( bucket_arn=ingest_bucket.bucket_arn, buffering_hints=firehose.CfnDeliveryStream.BufferingHintsProperty( interval_in_seconds=60, size_in_m_bs=5), compression_format="UNCOMPRESSED", role_arn=firehose_s3_role.role_arn, ) firehose_test = firehose.CfnDeliveryStream( self, "kinesis_firehose_recorder_test", s3_destination_configuration=s3_dest_config) self._parameters_to_save = { "firehose_name": firehose_test.ref, "identity_pool_id": identity_pool.ref, } self.save_parameters_in_parameter_store(platform=Platform.ANDROID) common_stack.add_to_common_role_policies(self)
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) ### S3 ### source_csv_bucket = _s3.Bucket(self, "BYODValidationSourceBucket", versioned=True) target_csv_bucket = _s3.Bucket( self, "BYODValidationTargetBucket", removal_policy=core.RemovalPolicy.RETAIN) ### Cognito ### userpool = _cognito.UserPool(self, "WebToolUserPool", user_pool_name="byod-webtool-userpool", self_sign_up_enabled=True, auto_verify={ "email": True, "phone": False }, user_verification={ "email_subject": "Your verification code", "email_body": "Your verification code is {####}", "email_style": _cognito.VerificationEmailStyle.CODE }, standard_attributes={ "email": { "required": True, "mutable": False } }, password_policy={}) client = userpool.add_client("webtool-app-client", auth_flows={ "custom": True, "user_password": True, "user_srp": True, "refresh_token": True }) identity_pool = _cognito.CfnIdentityPool( self, "WebToolCognitoIdentityPool", allow_unauthenticated_identities=True) identity_pool.add_property_override( "CognitoIdentityProviders", [{ "ClientId": client.user_pool_client_id, "ProviderName": userpool.user_pool_provider_name }]) auth_role = _iam.Role( self, "CognitoAuthRole", assumed_by=FederatedPrincipal( "cognito-identity.amazonaws.com", { "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" } })) auth_role.add_to_policy( PolicyStatement(effect=Effect.ALLOW, actions=["s3:GetObject"], resources=["%s/*" % target_csv_bucket.bucket_arn])) unauth_role = _iam.Role( self, "CognitoUnauthRole", assumed_by=_iam.FederatedPrincipal( "cognito-identity.amazonaws.com", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" } })) identity_pool_policy = _cognito.CfnIdentityPoolRoleAttachment( self, "WebToolCognitoIdentityPoolPolicy", identity_pool_id=identity_pool.ref, roles={ 'unauthenticated': unauth_role.role_arn, 'authenticated': auth_role.role_arn }) core.CfnOutput(self, "UserPoolId", value=userpool.user_pool_id) core.CfnOutput(self, "ClientId", value=client.user_pool_client_id) core.CfnOutput(self, "ProviderName", value=userpool.user_pool_provider_name) ### DynamoDB ### validation_job_table = _dynamodb.Table( self, "ValidationJobTable", partition_key=_dynamodb.Attribute( name="id", type=_dynamodb.AttributeType.STRING)) ## AppSync ### api = _appsync.GraphqlApi( self, "Api", name="validation-job-api", schema=_appsync.Schema.from_asset( os.path.join(dirname, "api", "schema.graphql")), authorization_config=AuthorizationConfig( default_authorization=AuthorizationMode( authorization_type=AuthorizationType.USER_POOL, user_pool_config=UserPoolConfig(user_pool=userpool))), log_config=LogConfig(exclude_verbose_content=False, field_log_level=FieldLogLevel.ALL)) api_ds = api.add_dynamo_db_data_source("ValidationJobDataSource", validation_job_table) core.CfnOutput(self, "GraphQLEndpoint", value=api.graphql_url) ### SQS ### validation_job_queue = _sqs.Queue(self, "ValidationJobQueue") profiling_job_queue = _sqs.Queue(self, "ProfilingJobQueue") ### Lambda ### validation_trigger_function = _lambda.Function( self, "ValidationTriggerFunction", runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.from_asset( os.path.join(dirname, "lambda", "validation_trigger")), handler='lambda_function.lambda_handler') validation_trigger_function.add_environment( "TABLE_NAME", validation_job_table.table_name) validation_trigger_function.add_environment( "QUEUE_URL", validation_job_queue.queue_url) validation_trigger_function.add_event_source( _S3EventSource(source_csv_bucket, events=[_s3.EventType.OBJECT_CREATED])) source_csv_bucket.grant_read(validation_trigger_function) validation_job_table.grant_read_write_data(validation_trigger_function) validation_job_queue.grant_send_messages(validation_trigger_function) stager_function = _lambda.Function(self, "StagerFunction", runtime=_lambda.Runtime.NODEJS_12_X, code=_lambda.Code.from_asset( os.path.join( dirname, "lambda", "stager")), handler='index.handler') stager_function.add_environment("REGION", self.region) stager_function.add_environment("SOURCE_BUCKET", source_csv_bucket.bucket_name) stager_function.add_environment("STAGE_BUCKET", target_csv_bucket.bucket_name) source_csv_bucket.grant_read(stager_function) target_csv_bucket.grant_put(stager_function) ### ECS Fargate ### validation_fargate_asset = _ecr_assets.DockerImageAsset( self, "ValidationBuildImage", directory=os.path.join(dirname, "fargate", "validation")) profiling_fargate_asset = _ecr_assets.DockerImageAsset( self, "ProfilingBuildImage", directory=os.path.join(dirname, "fargate", "profiling")) vpc = _ec2.Vpc(self, "VPC", max_azs=3) cluster = _ecs.Cluster(self, "ECSCluster", vpc=vpc) validation_fargate_service = _ecs_patterns.QueueProcessingFargateService( self, "ValidationFargateService", cluster=cluster, cpu=4096, memory_limit_mib=30720, enable_logging=True, image=_ecs.ContainerImage.from_docker_image_asset( validation_fargate_asset), environment={ "TABLE_NAME": validation_job_table.table_name, "QUEUE_URL": validation_job_queue.queue_url, "SOURCE_BUCKET_NAME": source_csv_bucket.bucket_name, "TARGET_BUCKET_NAME": target_csv_bucket.bucket_name, "REGION": self.region }, queue=validation_job_queue, max_scaling_capacity=2, max_healthy_percent=200, min_healthy_percent=66) validation_fargate_service.task_definition.task_role.add_managed_policy( _iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonDynamoDBFullAccess")) validation_fargate_service.task_definition.task_role.add_managed_policy( _iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonS3FullAccess")) profiling_fargate_service = _ecs_patterns.QueueProcessingFargateService( self, "ProfilingFargateService", cluster=cluster, cpu=4096, memory_limit_mib=30720, enable_logging=True, image=_ecs.ContainerImage.from_docker_image_asset( profiling_fargate_asset), environment={ "TABLE_NAME": validation_job_table.table_name, "QUEUE_URL": profiling_job_queue.queue_url, "SOURCE_BUCKET_NAME": source_csv_bucket.bucket_name, "TARGET_BUCKET_NAME": target_csv_bucket.bucket_name, "REGION": self.region }, queue=profiling_job_queue, max_scaling_capacity=2, max_healthy_percent=200, min_healthy_percent=66) profiling_fargate_service.task_definition.task_role.add_managed_policy( _iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonDynamoDBFullAccess")) profiling_fargate_service.task_definition.task_role.add_managed_policy( _iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonS3FullAccess"))
def __init__(self, scope: core.Construct, id: str, domain_prefix: str, other_account: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) user_pool = cognito.CfnUserPool( scope=self, id="user-pool", admin_create_user_config=cognito.CfnUserPool.AdminCreateUserConfigProperty( allow_admin_create_user_only=True, ), policies=cognito.CfnUserPool.PoliciesProperty( password_policy=cognito.CfnUserPool.PasswordPolicyProperty( minimum_length=20, )), username_attributes=["email"], auto_verified_attributes=["email"], ) cognito.CfnUserPoolDomain( scope=self, id="cognito-user-pool-domain", domain=f"{domain_prefix}-{core.Aws.ACCOUNT_ID}", user_pool_id=user_pool.ref, ) id_pool = cognito.CfnIdentityPool( scope=self, id="identity-pool", allow_unauthenticated_identities=False, cognito_identity_providers=[], ) auth_role = iam.Role( scope=self, id="auth-role", assumed_by=iam.FederatedPrincipal( federated="cognito-identity.amazonaws.com", conditions={ "StringEquals": {"cognito-identity.amazonaws.com:aud": id_pool.ref}, "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"}, }, assume_role_action="sts:AssumeRoleWithWebIdentity"), ) es_role = iam.Role( scope=self, id="es-role", assumed_by=iam.ServicePrincipal('es.amazonaws.com'), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( managed_policy_name="AmazonESCognitoAccess" ) ], ) es_domain = elasticsearch.CfnDomain( scope=self, id="search-domain", elasticsearch_cluster_config=elasticsearch.CfnDomain.ElasticsearchClusterConfigProperty( instance_count=2, instance_type="m5.large.elasticsearch", ), node_to_node_encryption_options=elasticsearch.CfnDomain.NodeToNodeEncryptionOptionsProperty( enabled=True), encryption_at_rest_options=elasticsearch.CfnDomain.EncryptionAtRestOptionsProperty( enabled=True), ebs_options=elasticsearch.CfnDomain.EBSOptionsProperty( ebs_enabled=True, volume_size=20), elasticsearch_version="7.4", domain_name=domain_prefix, access_policies={ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": auth_role.role_arn }, "Action": [ "es:ESHttpGet", "es:ESHttpPut", "es:ESHttpPost", "es:ESHttpDelete" ], "Resource": "arn:aws:es:" + core.Aws.REGION + ":" + core.Aws.ACCOUNT_ID + ":domain/" + domain_prefix + "/*" }, ] }, ) es_domain.add_property_override( 'CognitoOptions.Enabled', True) es_domain.add_property_override( 'CognitoOptions.IdentityPoolId', id_pool.ref) es_domain.add_property_override( 'CognitoOptions.RoleArn', es_role.role_arn) es_domain.add_property_override( 'CognitoOptions.UserPoolId', user_pool.ref) cognito.CfnIdentityPoolRoleAttachment( scope=self, id='user-pool-role-attachment', identity_pool_id=id_pool.ref, roles={ 'authenticated': auth_role.role_arn } ) es_external_role = iam.Role( scope=self, id="logger-role", assumed_by=iam.CompositePrincipal( iam.ServicePrincipal("lambda.amazonaws.com"), iam.AccountPrincipal(other_account), ), description="role to use elastic search assumed by lambda", inline_policies={ "es_policy": iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "es:ESHttpPost", ], resources=[ es_domain.attr_arn + "/*", ], )]), }, ) core.CfnOutput( scope=self, id="es-host", value=es_domain.attr_domain_endpoint, ) core.CfnOutput( scope=self, id="es-region", value=core.Aws.REGION, ) core.CfnOutput( scope=self, id="es-external-role", value=es_external_role.role_arn, )
def __init__(self, scope: core.Construct, id: str, aws_region: str, account_id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) s3.Bucket(self, 'administrativedashboard', bucket_name='administrative-dashboard' + account_id, block_public_access=s3.BlockPublicAccess.BLOCK_ALL, versioned=True) lambda_role = iam.Role( self, id='lambda-role', description= 'Role for the quicksight administrative dashboard lambda', role_name='qs-administrative-dashboard-lambda', max_session_duration=core.Duration.seconds(3600), assumed_by=iam.ServicePrincipal('lambda.amazonaws.com'), inline_policies={ 'AllowS3Access': iam.PolicyDocument(statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "lambda:InvokeFunction", "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents", "quicksight:*", "s3:HeadBucket", "s3:ListAllMyBuckets", "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:GetObjectVersionForReplication", "s3:GetBucketPolicy", "s3:GetObjectVersion", "cloudwatch:PutMetricData", "sts:GetCallerIdentity" ], resources=['*']) ]) }) user_initiation = _lambda.Function( self, 'user_initiation', handler='user_initiation.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset( os.path.join(current_dir, '../lambda_functions/user_initiation/')), function_name='user_initiation', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=512) group_initiation = _lambda.Function( self, 'group_initiation', handler='group_initiation.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset( os.path.join(current_dir, '../lambda_functions/group_initiation/')), function_name='group_initiation', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=512) data_prepare = _lambda.Function( self, 'data_prepare', handler='data_prepare.lambda_handler', runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.from_asset( os.path.join(current_dir, '../lambda_functions/data_prepare/')), function_name='data_prepare', role=lambda_role, timeout=core.Duration.minutes(15), memory_size=512) usercreation = events.Rule( self, 'usercreation', description= 'CloudWatch rule to detect new QuickSight user creation', rule_name='quicksight-user-creation', targets=[ targets.LambdaFunction(user_initiation), targets.LambdaFunction(data_prepare) ], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS Service Event via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["CreateUser"] })) groupcreation = events.Rule( self, 'groupcreation', description= 'CloudWatch rule to detect new QuickSight group creation', rule_name='quicksight-group-creation', targets=[ targets.LambdaFunction(group_initiation), targets.LambdaFunction(data_prepare) ], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["CreateGroup"] })) groupdeletion = events.Rule( self, 'groupdeletion', description='CloudWatch rule to detect QuickSight group deletion', rule_name='quicksight-group-deletion', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["DeleteGroup"] })) userdeletion = events.Rule( self, 'userdeletion', description='CloudWatch rule to detect QuickSight user deletion', rule_name='quicksight-user-deletion', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS Service Event via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["DeleteUser"] })) addgroupmember = events.Rule( self, 'addgroupmember', description= 'CloudWatch rule to detect QuickSight user add into a group', rule_name='quicksight-user-add-into-a-group', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["CreateGroupMembership"] })) dashboardupdate = events.Rule( self, 'dashboardupdate', description= 'CloudWatch rule to detect QuickSight dashboard permissions update', rule_name='quicksight-dashboard-permissions-update', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["UpdateDashboardPermissions"] })) datasetupdate = events.Rule( self, 'datasetupdate', description= 'CloudWatch rule to detect QuickSight dataset permissions update', rule_name='quicksight-dataset-permissions-update', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["UpdateDataSetPermissions"] })) datasourceupdate = events.Rule( self, 'datasourceupdate', description= 'CloudWatch rule to detect QuickSight data source permissions update', rule_name='quicksight-datasource-permissions-update', targets=[targets.LambdaFunction(data_prepare)], event_pattern=events.EventPattern( source=['aws.quicksight'], detail_type=['AWS API Call via CloudTrail'], detail={ "eventSource": ["quicksight.amazonaws.com"], "eventName": ["UpdateDataSourcePermissions"] })) quicksight_assume_condition_object = { "StringEquals": { "SAML:aud": "https://signin.aws.amazon.com/saml" } } quicksight_federated_prin_with_conditionb_obj = iam.FederatedPrincipal( f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:saml-provider/saml', quicksight_assume_condition_object, 'sts:AssumeRoleWithSAML') quicksight_resource_scope = '${aws:userid}' quicksight_reader_saml_inline_policies = { 'AllowQuicksightAccessSAML': iam.PolicyDocument(statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['quicksight:CreateReader'], resources=[ f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}' ]) ]) } Marketing = iam.Role( self, id="Marketing", #quicksight-saml-<ldap group name> description='Role for the quicksight reader SAML', role_name="Marketing", max_session_duration=core.Duration.seconds(3600), assumed_by=quicksight_federated_prin_with_conditionb_obj, inline_policies=quicksight_reader_saml_inline_policies) HR = iam.Role(self, id="HR", description='Role for the quicksight reader SAML', role_name="HR", max_session_duration=core.Duration.seconds(3600), assumed_by=quicksight_federated_prin_with_conditionb_obj, inline_policies=quicksight_reader_saml_inline_policies) quicksight_author_saml_inline_policies = { 'AllowQuicksightAuthorAccessSAML': iam.PolicyDocument(statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['quicksight:CreateUser'], resources=[ f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}' ]) ]) } bideveloper = iam.Role( self, id="bideveloper", description='Role for the quicksight author SAML', role_name="BI-Developer", max_session_duration=core.Duration.seconds(3600), assumed_by=quicksight_federated_prin_with_conditionb_obj, inline_policies=quicksight_author_saml_inline_policies) quicksight_admin_saml_inline_policies = { 'AllowQuicksightAdminAccessSAML': iam.PolicyDocument(statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['quicksight:CreateAdmin'], resources=[ f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}' ]) ]) } Admin = iam.Role( self, id="Admin", description='Role for the quicksight admin SAML', role_name="BI-Admin", max_session_duration=core.Duration.seconds(3600), assumed_by=quicksight_federated_prin_with_conditionb_obj, inline_policies=quicksight_admin_saml_inline_policies)
def build_team_role( scope: core.Construct, context: "Context", team_name: str, policy_names: List[str], scratch_bucket: s3.IBucket, team_kms_key: kms.Key, session_timeout: core.Duration, ) -> iam.Role: env_name = context.name partition = core.Aws.PARTITION account = core.Aws.ACCOUNT_ID region = core.Aws.REGION lake_role_name: str = f"orbit-{env_name}-{team_name}-{region}-role" role_prefix: str = f"/{context.role_prefix}/" if context.role_prefix else "/" kms_keys = [team_kms_key.key_arn] scratch_bucket_kms_key = IamBuilder.get_kms_key_scratch_bucket( context=context) if scratch_bucket_kms_key: kms_keys.append(scratch_bucket_kms_key) lake_operational_policy = iam.ManagedPolicy( scope=scope, id="lake_operational_policy", managed_policy_name= f"orbit-{env_name}-{team_name}-{region}-user-access", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "s3:*", ], resources=[ f"arn:{partition}:s3:::sagemaker-{region}-{account}", f"arn:{partition}:s3:::sagemaker-{region}-{account}/*", scratch_bucket.bucket_arn, f"{scratch_bucket.bucket_arn}/{team_name}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["s3:List*", "s3:Get*", "s3:Put*"], resources=[ f"arn:{partition}:s3:::{context.toolkit.s3_bucket}", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/samples/*", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/codeseeder/*", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/teams/{team_name}/*", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/helm/repositories/env/*", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/helm/repositories/teams/{team_name}/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["ssm:Get*"], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit*", f"arn:{partition}:ssm:{region}:{account}:parameter/emr_launch/", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["ssm:PutParameter"], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/user*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "redshift:GetClusterCredentials", "redshift:CreateClusterUser", "redshift:DeleteCluster", ], resources=[ f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*", f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*/master", f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*/defaultdb", f"arn:{partition}:redshift:{region}:{account}:dbname:orbit-{env_name}-{team_name}*/defaultdb", f"arn:{partition}:redshift:{region}:{account}:cluster:orbit-{env_name}-{team_name}*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "sns:*", ], resources=[ f"arn:{partition}:sns:{region}:{account}:{env_name}-{team_name}*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["iam:PassRole"], resources=[ f"arn:{partition}:iam::{account}:role{role_prefix}{lake_role_name}" ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:Describe*", "codeartifact:Describe*", "codeartifact:Get*", "codeartifact:List*", "codeartifact:Read*", "sts:GetServiceBearerToken", "s3:ListAllMyBuckets", "lambda:List*", "lambda:Get*", "iam:List*", "tag:GetResources", "ecr:Get*", "ecr:List*", "ecr:Describe*", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability", "cloudwatch:PutMetricData", "redshift:DescribeClusters", "states:List*", "states:Get*", "states:Describe*", "glue:Get*", "glue:List*", "glue:Search*", "athena:*", "ecs:Describe*", "ecs:ListTasks", "ec2:Describe*", "redshift:DescribeClusters", "elasticmapreduce:List*", "elasticmapreduce:Get*", "elasticmapreduce:Describe*", "elasticmapreduce:TerminateJobFlows", "elasticmapreduce:AddJobFlowSteps", "sagemaker:*", "databrew:*", "lakeformation:GetDataAccess", "fsx:Describe*", "fsx:List*", ], resources=["*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ecr:*", ], resources=[ f"arn:{partition}:ecr:{region}:{account}:repository/orbit-{env_name}/users/*" ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], resources=kms_keys, ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "lambda:InvokeFunction", ], resources=[ f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-{team_name}-*", f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-token-validation", f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-eks-service-handler", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "cloudformation:DescribeStacks", ], resources=[ f"arn:{partition}:cloudformation:{region}:{account}:stack/orbit-{env_name}/*", f"arn:{partition}:cloudformation:{region}:{account}:stack/aws-codeseeder-orbit*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:GetParameters", "ssm:DescribeParameters", "ssm:GetParameter", "ssm:DescribeParameter", ], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/*", f"arn:{partition}:ssm:{region}:{account}:parameter/Orbit-Slack-Notifications", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:DeleteParameter", "ssm:DeleteParameters", ], resources=[ f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/changeset", f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/manifest", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ssm:DescribeParameters", ], resources=[f"arn:{partition}:ssm:{region}:{account}:*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["s3:Put*"], resources=[ f"arn:{partition}:s3:::{context.toolkit.s3_bucket}", f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/cli/remote/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "codebuild:StartBuild", "codebuild:BatchGetBuilds" ], resources=[ f"arn:{partition}:codebuild:{region}:{account}:project/orbit-{env_name}", f"arn:{partition}:codebuild:{region}:{account}:project/codeseeder-orbit", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:DescribeLogStreams", "logs:PutLogEvents", ], resources=[ f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}:log-stream:*", # noqa f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/codeseeder-orbit:log-stream:*", # noqa f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*:log-stream:*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "logs:List*", "logs:Describe*", "logs:StartQuery", "logs:StopQuery", "logs:Get*", "logs:Filter*", "events:*", ], resources=[ f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}*:log-stream:*", # noqa f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/codeseeder-orbit:log-stream:*", # noqa f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*:log-stream:*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*:log-stream:*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws/eks/orbit*", f"arn:{partition}:events:{region}:{account}:rule/orbit-{env_name}-{team_name}-*", f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "ecr:InitiateLayerUpload", ], resources=[ f"arn:{partition}:ecr:{region}:{account}:repository/*", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=[ "eks:DescribeCluster", ], resources=[ f"arn:{partition}:eks:{region}:{account}:cluster/orbit-{env_name}", ], ), ], ) managed_policies = [ lake_operational_policy, # For EKS iam.ManagedPolicy.from_aws_managed_policy_name( managed_policy_name="AmazonEKS_CNI_Policy"), ] # Parse list to IAM policies # First check if the policies are AWS managed or not, and if they have a tag aws_policies, customer_policies = process_policies( policy_names=policy_names, account_id=context.account_id) aws_managed_user_policies = [ iam.ManagedPolicy.from_aws_managed_policy_name( managed_policy_name=policy_name) for policy_name in aws_policies ] orbit_custom_policies = [ iam.ManagedPolicy.from_managed_policy_name( scope=scope, id=policy_name, managed_policy_name=policy_name) for policy_name in customer_policies ] managed_policies = (managed_policies + cast(List[object], aws_managed_user_policies) + cast(List[object], orbit_custom_policies)) role = iam.Role( scope=scope, id=f"lakerole-for-{env_name}-{team_name}", role_name=lake_role_name, assumed_by=cast( iam.IPrincipal, iam.CompositePrincipal( iam.ServicePrincipal("ec2.amazonaws.com"), iam.ServicePrincipal("glue.amazonaws.com"), iam.ServicePrincipal("sagemaker.amazonaws.com"), iam.ServicePrincipal("redshift.amazonaws.com"), iam.ServicePrincipal("codepipeline.amazonaws.com"), iam.ServicePrincipal("codebuild.amazonaws.com"), iam.ServicePrincipal("personalize.amazonaws.com"), iam.ServicePrincipal("databrew.amazonaws.com"), ), ), managed_policies=cast(Optional[Sequence[iam.IManagedPolicy]], managed_policies), max_session_duration=session_timeout, ) if role.assume_role_policy: role.assume_role_policy.add_statements( iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=["sts:AssumeRoleWithWebIdentity"], principals=[ cast( iam.IPrincipal, iam.FederatedPrincipal( federated= f"arn:{partition}:iam::{account}:oidc-provider/{context.eks_oidc_provider}", conditions={ "StringLike": { f"{context.eks_oidc_provider}:sub": f"system:serviceaccount:{team_name}*:*" } }, ), ) ], ), ) return role
def __init__( self, scope: core.Construct, id: str, host_name, cert_arn, zone_id, admin_user: str, admin_password: str, cloud9_instance_size: str, participant_limit: str, **kwargs, ) -> None: super().__init__(scope, id, **kwargs) stack = core.Stack.of(self) stack.template_options.description = "Connected Drink Dispenser Workshop" # Static Website props: StaticSiteProps = StaticSiteProps( fqdn=host_name, hosted_zone_id=zone_id, certificate_arn=cert_arn, error_configuration=[ { "error_code": 403, "error_caching_min_ttl": 300, "response_code": 200, "response_page_path": "/index.html", }, { "error_code": 404, "error_caching_min_ttl": 300, "response_code": 200, "response_page_path": "/index.html", }, ], output_name="CDDWebSite", ) cdd_site = StaticSiteConstruct(self, "StaticSite", props) # Custom resource to clean out static website bucket prior to delete # TODO: Move this to the StaticSiteConstruct as option props: CustomResourceProps = CustomResourceProps( name=id + "-CR-S3DeleteObjects", lambda_directory="./lambda_functions/cr_s3_delete", handler="index.main", timeout=30, runtime=lambda_.Runtime.PYTHON_3_7, environment={"BUCKET_NAME": cdd_site.bucket_name}, ) s3_delete_cr = CustomResourceConstruct(self, "EmptyCddS3Bucket", props) # DependsOn the bucket (we need to delete objects before the bucket is deleted) s3_delete_cr.resource.node.add_dependency(cdd_site.bucket_resource) policy_statement = iam.PolicyStatement() policy_statement.add_actions("s3:GetBucket*") policy_statement.add_actions("s3:GetObject*") policy_statement.add_actions("s3:DeleteObject*") policy_statement.add_actions("s3:List*") policy_statement.add_resources(cdd_site.bucket_resource.bucket_arn) policy_statement.add_resources( f"{cdd_site.bucket_resource.bucket_arn}/*") s3_delete_cr.add_policy_to_role(policy_statement) # IAM Constructs user_group = iam.Group( self, "UserGroup", group_name=id + "-CDDUserGroup", managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( "ReadOnlyAccess") ], ) # DynamoDB tables user_db = dynamodb.Table( # UserId as key, user "admin" tracks next available dispenser id # No access to users, RW to Cognito Lambda self, "UserTable", table_name=id + "-UserTable", partition_key={ "name": "userName", "type": dynamodb.AttributeType.STRING }, billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, removal_policy=core.RemovalPolicy.DESTROY, ) dispenser_db = dynamodb.Table( # Dispenser ID and credit amount - RO to users, RW to APIs self, "DispenserTable", table_name=id + "-DispenserTable", partition_key={ "name": "dispenserId", "type": dynamodb.AttributeType.STRING, }, billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, removal_policy=core.RemovalPolicy.DESTROY, ) dispenser_events = dynamodb.Table( # Recorded events from dispenser actions self, "DispenserEvents", table_name=id + "-DispenserEvents", partition_key={ "name": "dispenserId", "type": dynamodb.AttributeType.STRING, }, sort_key={ "name": "timestamp", "type": dynamodb.AttributeType.STRING }, billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, removal_policy=core.RemovalPolicy.DESTROY, ) # Cognito Resources # User pool with phone_number as username props: CognitoUserPoolProps = CognitoUserPoolProps( user_pool_name=id + "-users", client_name=id + "-webclient", auto_verified_attributes=["phone_number"], schema=[ { "name": "group", "attributeDataType": "String", "mutable": True, "required": False, }, { "name": "dispenserId", "attributeDataType": "String", "mutable": True, "required": False, }, ], policies={ "passwordPolicy": { "minimumLength": 6, "requireLowercase": True, "requireNumbers": True, "requireSymbols": False, "requireUppercase": False, } }, ) user_pool = CognitoUserPoolConstruct(self, "UserPool", props) # Role and lambda triggers lambda_cognito_access_role = iam.Role( # Access to IDP calls (for triggers) self, "LambdaCognitoAccessRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement(actions=["cognito-idp:*"], resources=["*"]), iam.PolicyStatement(actions=["dynamodb:*"], resources=["*"]), ]) ], ) # Triggers for UserPool # Pre-sign-up: triggered when username, password, and phone number submitted lambda_cognito_trigger_pre_signup = lambda_.Function( self, "CogntioTriggerPreSignUp", function_name=id + "-CogntioTriggerPreSignUp", code=lambda_.AssetCode("./lambda_functions/cog_pre_signup"), handler="lambda.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_cognito_access_role, timeout=core.Duration.seconds(6), environment={ "USER_TABLE": user_db.table_name, "PARTICIPANT_LIMIT": participant_limit, }, ) lambda_cognito_trigger_pre_signup.add_permission( "AllowCognitoPreSign", principal=iam.ServicePrincipal("cognito-idp.amazonaws.com"), source_arn=user_pool.user_pool_arn, ) # Post confirmation: triggered after validation code provided lambda_cognito_trigger_post_confirm = lambda_.Function( self, "CogntioTriggerPostConfirm", function_name=id + "-CogntioTriggerPostConfirm", code=lambda_.AssetCode("./lambda_functions/cog_post_confirm"), handler="lambda.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_cognito_access_role, timeout=core.Duration.seconds(6), environment={ "USER_TABLE": user_db.table_name, "PARTICIPANT_LIMIT": participant_limit, }, ) lambda_cognito_trigger_post_confirm.add_permission( "AllowCognitoPostConfirm", principal=iam.ServicePrincipal("cognito-idp.amazonaws.com"), source_arn=user_pool.user_pool_arn, ) # Attach triggers to pool user_pool.user_pool.lambda_config = cognito.CfnUserPool.LambdaConfigProperty( pre_sign_up=lambda_cognito_trigger_pre_signup.function_arn, post_confirmation=lambda_cognito_trigger_post_confirm.function_arn, ) cognito.CfnUserPoolGroup( self, "UserPoolCDDUser", group_name="cdd_user", description="General users of CDD (participants)", user_pool_id=user_pool.user_pool_id, ) cognito.CfnUserPoolGroup( self, "UserPoolCDDAdmin", group_name="cdd_admin", description="CDD administrators", user_pool_id=user_pool.user_pool_id, ) identity_pool = cognito.CfnIdentityPool( self, "IdentityPool", identity_pool_name=id.replace("-", "") + "_idpool", allow_unauthenticated_identities=False, cognito_identity_providers=[{ "clientId": user_pool.client_id, "providerName": user_pool.provider_name, }], ) core.CfnOutput( self, "CognitoIdentityPoolId", export_name="CognitoIdentityPoolId", value=identity_pool.ref, ) # Custom resource to create admin user - cannot do via CFn to set password props: CustomResourceProps = CustomResourceProps( name=id + "-CR-CreateCognitoAdminUser", lambda_directory="./lambda_functions/cr_create_admin_user", handler="index.main", timeout=30, runtime=lambda_.Runtime.PYTHON_3_7, environment={ "COGNITO_USER_POOL_ID": user_pool.user_pool_id, "COGNITO_CLIENT_ID": user_pool.client_id, "ADMIN_USERNAME": admin_user, "ADMIN_PASSWORD": admin_password, }, ) create_admin_user_cr = CustomResourceConstruct(self, "CreateAdminUser", props) # DependsOn the user pool create_admin_user_cr.resource.node.add_dependency(user_pool) policy_statement = iam.PolicyStatement() policy_statement.add_actions("cognito-idp:SignUp") policy_statement.add_actions("cognito-idp:AdminConfirmSignUp") policy_statement.add_resources("*") create_admin_user_cr.add_policy_to_role(policy_statement) # IAM roles for identity pool auth/unauth cog_unauth_role = iam.Role( self, "cognitoUnauthRole", role_name=f"Cognito_{identity_pool.identity_pool_name}_Unauth_Role", assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" }, }, assume_role_action="sts:AssumeRoleWithWebIdentity", ), ) cog_unauth_role.attach_inline_policy( iam.Policy( self, "cognitoUnauth", policy_name="cognitoUnauth", statements=[ iam.PolicyStatement( actions=[ "mobileanalytics:PutEvents", "cognito-sync:*" ], resources=["*"], ) ], )) cog_auth_role = iam.Role( self, "cognitoAuthRole", role_name=f"Cognito_{identity_pool.identity_pool_name}_Auth_Role", managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonESCognitoAccess") ], assumed_by=iam.FederatedPrincipal( "cognito-identity.amazonaws.com", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" }, }, assume_role_action="sts:AssumeRoleWithWebIdentity", ), ) cog_auth_role.attach_inline_policy( iam.Policy( self, "cognitoAuth", policy_name="cognitoAuth", statements=[ iam.PolicyStatement( actions=[ "mobileanalytics:PutEvents", "cognito-sync:*", "execute-api:*", ], resources=["*"], ), # Provide full access to IoT for the authenticated user # The AWS IoT policy scopes down the access iam.PolicyStatement(actions=["iot:*"], resources=["*"]), ], )) # Finally, attach auth and unauth roles to Identity pool cognito.CfnIdentityPoolRoleAttachment( self, "CDDIdentityPoolRoleAttach", identity_pool_id=identity_pool.ref, roles={ "authenticated": cog_auth_role.role_arn, "unauthenticated": cog_unauth_role.role_arn, }, ) ### Supporting IAM Roles and Policies lambda_full_access_role = iam.Role( # Wide open role for Lambda's to access other services self, "LambdaFullAccessRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement(actions=["*"], resources=["*"]), ]) ], ) lambda_iot_full_access_role = iam.Role( # Wide open role for Lambda's to access other services self, "LambdaIoTFullAccessRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement(actions=["dynamodb:*", "iot:*"], resources=["*"]), ]) ], ) lambda_api_app_role = iam.Role( # Role for APIG Lambda functions - make specific per Lambda/method if needed self, "ApiAppRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement( actions=["dynamodb:*"], resources=[ f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}", f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}", f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}", ], ), iam.PolicyStatement(actions=["iot:*"], resources=["*"]), ]) ], ) lambda_api_delete_user_role = iam.Role( # Role for APIG Lambda delete user - specific as this has to delete multiple services self, "ApiDeleteUserRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement( actions=["dynamodb:*"], resources=[ f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}", f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}", f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}", ], ), iam.PolicyStatement(actions=["cloud9:DeleteEnvironment"], resources=["*"]), iam.PolicyStatement( actions=[ "iam:DeleteLoginProfile", "iam:ListGroupsForUser", "iam:RemoveUserFromGroup", "iam:DeleteUser", ], resources=["*"], ), iam.PolicyStatement( actions=["cognito-idp:AdminDeleteUser"], resources=["*"]), iam.PolicyStatement(actions=["iot:*"], resources=["*"]), ]) ], ) lambda_api_dispense_role = iam.Role( # Role for lambda self, "CommandRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), inline_policies=[ iam.PolicyDocument(statements=[ iam.PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], resources=["arn:aws:logs:*:*:*"], ), iam.PolicyStatement( actions=["dynamodb:*"], resources=[ f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}", f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}", ], ), iam.PolicyStatement(actions=["iot:*"], resources=["*"]), ]) ], ) # IoT Policies iot_policy_dispenser_limited = iot.CfnPolicy( self, "IoTDispenserLimitedPolicy", policy_name=id + "-DispenserLimitedAccess", policy_document={ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["iot:Connect"], "Resource": [ f"arn:aws:iot:{stack.region}:{stack.account}:client/${{iot:Connection.Thing.ThingName}}" ], "Condition": { "Bool": { "iot:Connection.Thing.IsAttached": [True] } }, }, { "Effect": "Allow", "Action": ["iot:Receive"], "Resource": ["*"] }, { "Effect": "Allow", "Action": ["iot:Subscribe"], "Resource": [ f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/*", f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/${{iot:Certificate.Subject.CommonName}}/cmd/${{iot:Certificate.Subject.CommonName}}", ], }, { "Effect": "Allow", "Action": ["iot:Publish"], "Resource": [ f"arn:aws:iot:{stack.region}:{stack.account}:topic/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/update", f"arn:aws:iot:{stack.region}:{stack.account}:topic/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/get", f"arn:aws:iot:{stack.region}:{stack.account}:topic/test/${{iot:Certificate.Subject.CommonName}}", f"arn:aws:iot:{stack.region}:{stack.account}:topic/cmd/${{iot:Certificate.Subject.CommonName}}/response", ], }, ], }, ) iot_policy_client = iot.CfnPolicy( self, "IoTClientPolicy", policy_name=id + "-IoTClientAccess", policy_document={ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["iot:Connect", "iot:Receive"], "Resource": ["*"], }, { "Effect": "Allow", "Action": ["iot:Subscribe"], "Resource": [ f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/events/*", f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/*/shadow/update/accepted", ], }, ], }, ) ### Lambda Functions # General Lambda Functions NOT associated with APIG lambda_process_events = lambda_.Function( self, "ProcessEvents", function_name=id + "-ProcessEvents", code=lambda_.AssetCode("./lambda_functions/process_events"), handler="process_events.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_iot_full_access_role, timeout=core.Duration.seconds(20), environment={ "EVENT_TABLE": dispenser_events.table_name, "STATUS_TABLE": dispenser_db.table_name, }, ) ## API Lambda functions # Return credit for dispenser api_credit_dispenser_function = lambda_.Function( self, "ApiCreditDispenserFunction", function_name=id + "-ApiCreditDispenserFunction", code=lambda_.AssetCode("./lambda_functions/api_credit_dispenser"), handler="credit_dispenser.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_api_app_role, timeout=core.Duration.seconds(15), memory_size=128, environment={ "DISPENSER_TABLE": dispenser_db.table_name, "EVENT_TABLE": dispenser_events.table_name, }, ) # Command api_command_function = lambda_.Function( self, "ApiCommandFunction", function_name=id + "-ApiCommandFunction", code=lambda_.AssetCode("./lambda_functions/api_command"), handler="command.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_api_app_role, timeout=core.Duration.seconds(15), memory_size=128, environment={ "DispenserTable": dispenser_db.table_name, "EventTable": dispenser_events.table_name, }, ) # Request dispense operation (set shadow or command to dispense) api_dispense_function = lambda_.Function( self, "ApiDispenseFunction", function_name=id + "-ApiDispenseFunction", code=lambda_.AssetCode("./lambda_functions/api_dispense"), handler="dispense.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_api_dispense_role, timeout=core.Duration.seconds(15), memory_size=128, environment={ "DISPENSER_TABLE": dispenser_db.table_name, "EVENT_TABLE": dispenser_events.table_name, }, ) # Request dispense operation (set shadow or command to dispense) api_dispenser_status_function = lambda_.Function( self, "ApiDispenserStatusFunction", function_name=id + "-ApiDispenserStatusFunction", code=lambda_.AssetCode("./lambda_functions/api_dispenser_status"), handler="dispenser_status.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_api_app_role, timeout=core.Duration.seconds(15), memory_size=128, environment={"DISPENSER_TABLE": dispenser_db.table_name}, ) # Request user details from user table, create resources if needed # NOTE: This uses an overley permissive policy to create the resources needed api_get_resources_function = lambda_.Function( self, "ApiGetResourcesFunction", function_name=id + "-ApiGetResourcesFunction", code=lambda_.AssetCode("./lambda_functions/api_get_resources"), handler="get_resources.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_full_access_role, # Timeout is for user creation: certain tasks such as Cloud9 may take longer timeout=core.Duration.seconds(300), memory_size=128, environment={ "DISPENSER_TABLE": dispenser_db.table_name, "EVENT_TABLE": dispenser_events.table_name, "USER_TABLE": user_db.table_name, "USER_PERMISSIONS_GROUP": user_group.group_name, "IOT_POLICY_DISPENSER_LIMITED": iot_policy_dispenser_limited.policy_name, "IOT_POLICY_CLIENT": iot_policy_client.policy_name, "CLOUD9_INSTANCE_SIZE": cloud9_instance_size, }, ) # Request user details from user table api_delete_user_function = lambda_.Function( self, "ApiDeleteUserFunction", function_name=id + "-ApiDeleteUserFunction", code=lambda_.AssetCode("./lambda_functions/api_delete_user"), handler="delete_user.handler", runtime=lambda_.Runtime.PYTHON_3_7, role=lambda_api_delete_user_role, timeout=core.Duration.seconds(28), memory_size=256, environment={ "DISPENSER_TABLE": dispenser_db.table_name, "EVENT_TABLE": dispenser_events.table_name, "USER_TABLE": user_db.table_name, "USER_POOL_ID": user_pool.user_pool_id, }, ) ### API Gateway api = apigateway.RestApi( self, id + "-API", api_key_source_type=apigateway.ApiKeySourceType.HEADER, deploy_options=apigateway.StageOptions(stage_name="prod"), ) core.CfnOutput( self, "APIEndpoint", export_name="APIEndpoint", value= f"https://{api.rest_api_id}.execute-api.{stack.region}.amazonaws.com/prod/", ) # Although / is not used as method, provide OPTIONS for hinting CORS add_cors_options(api.root) # Define Cognito authorizer and attach to gateway cog_authorizer = apigateway.CfnAuthorizer( self, "CognitoAuth", name="CognitoAuthName", rest_api_id=api.rest_api_id, type="COGNITO_USER_POOLS", identity_source="method.request.header.Authorization", provider_arns=[user_pool.user_pool_arn], ) # # Resources (paths) and methods (GET, POST, etc.), for the API api_credit_resource = api.root.add_resource("credit") add_resource_method( api_credit_resource, http_method="GET", integration=apigateway.LambdaIntegration( api_credit_dispenser_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_credit_resource) # command api_command_resource = api.root.add_resource("command") add_resource_method( api_command_resource, http_method="GET", integration=apigateway.LambdaIntegration(api_command_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_command_resource) # Actuate dispenser api_dispense_resource = api.root.add_resource("dispense") add_resource_method( api_dispense_resource, http_method="GET", integration=apigateway.LambdaIntegration(api_dispense_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_dispense_resource) # Return dispenser status (from DynamoDB) api_dispenser_status_resource = api.root.add_resource("status") add_resource_method( api_dispenser_status_resource, http_method="GET", integration=apigateway.LambdaIntegration( api_dispenser_status_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_dispenser_status_resource) # Return user details from User Table api_get_resources_resource = api.root.add_resource("getResources") add_resource_method( api_get_resources_resource, http_method="POST", integration=apigateway.LambdaIntegration( api_get_resources_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_get_resources_resource) # Create a user based on valid token api_delete_user_resource = api.root.add_resource("deleteUser") add_resource_method( api_delete_user_resource, http_method="POST", integration=apigateway.LambdaIntegration(api_delete_user_function), authorization_type=apigateway.AuthorizationType.COGNITO, authorizer=cog_authorizer, ) add_cors_options(api_delete_user_resource) # Create policy and reference group iam.Policy( self, "UserPermissionsPolicy", groups=[user_group], policy_name=id + "-UserPermissions", statements=[ # Elevated permissions beyond the ReadOnlyUser # Allow seeing all MQTT messages iam.PolicyStatement( actions=["iot:Subscribe", "iot:Connect", "iot:Receive"], resources=["*"], ), # Allow search indexing iam.PolicyStatement( actions=["iot:SearchIndex"], resources=[ f"arn:aws:iot:{stack.region}:{stack.account}:index/AWS_Things" ], ), # Allow changing of security group ingress on EC2 (Cloud9) to support mapping 443 to iam.PolicyStatement( actions=[ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress", ], resources=[ f"arn:aws:ec2:{stack.region}:{stack.account}:security-group/*" ], ), # DENY access to credentials table iam.PolicyStatement( effect=iam.Effect.DENY, actions=["dynamodb:*"], resources=[ f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}" ], ), # DENY access to S3 overall iam.PolicyStatement(effect=iam.Effect.DENY, actions=["s3:*"], resources=["*"]), ], ) # IoT Constructs # Rule to process shadow events and send to logging iot_rule_log_shadow_events = iot.CfnTopicRule( self, "LogShadowEventsRule", rule_name=id.replace("-", "") + "_LogShadowEvents", topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty( description= "Based on shadow topic and content, process messages via Lambda", rule_disabled=False, aws_iot_sql_version="2016-03-23", sql= "select *, topic() AS topic FROM '$aws/things/+/shadow/update/documents'", actions=[ iot.CfnTopicRule.ActionProperty( lambda_=iot.CfnTopicRule.LambdaActionProperty( function_arn=lambda_process_events.function_arn)) ], ), ) # Allow rule to invoke the logging function lambda_process_events.add_permission( "AllowIoTRule1", principal=iam.ServicePrincipal("iot.amazonaws.com"), source_arn=iot_rule_log_shadow_events.attr_arn, ) # Rule to process generic events and send to logging iot_rule_log_generic_events = iot.CfnTopicRule( self, "LogGenericEventsRule", rule_name=id.replace("-", "") + "_LogGenericEvents", topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty( description="Log generic events, enrich, then send to Lambda", rule_disabled=False, aws_iot_sql_version="2016-03-23", sql= "select *, timestamp() AS ts, topic() AS topic FROM 'events'", actions=[ iot.CfnTopicRule.ActionProperty( lambda_=iot.CfnTopicRule.LambdaActionProperty( function_arn=lambda_process_events.function_arn)) ], ), ) # Allow generic_events rule to Invoke the process_events function lambda_process_events.add_permission( "AllowIoTRule2", principal=iam.ServicePrincipal("iot.amazonaws.com"), source_arn=iot_rule_log_generic_events.attr_arn, ) # Rule to process dispenser specific events and send to logging iot_rule_log_dispenser_events = iot.CfnTopicRule( self, "LogDispenserEventsRule", rule_name=id.replace("-", "") + "_LogDispenserEvents", topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty( description= "Log specific dispenser events, enrich, then send to Lambda", rule_disabled=False, aws_iot_sql_version="2016-03-23", sql= "select *, timestamp() AS ts, topic() AS topic FROM 'events/+'", actions=[ iot.CfnTopicRule.ActionProperty( lambda_=iot.CfnTopicRule.LambdaActionProperty( function_arn=lambda_process_events.function_arn)) ], ), ) # Allow log_dispenser_events rule to Invoke the process_events function lambda_process_events.add_permission( "AllowIoTRule3", principal=iam.ServicePrincipal("iot.amazonaws.com"), source_arn=iot_rule_log_dispenser_events.attr_arn, ) # Rule to process cmd/NNN/response WHERE "command=dispense" iot_rule_command_response_dispense = iot.CfnTopicRule( self, "DispenseCommandResponseRule", rule_name=id.replace("-", "") + "_DispenseCommandResponse", topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty( description= "Invoke Lambda to process dispense commands from dispenser", rule_disabled=False, aws_iot_sql_version="2016-03-23", sql= "select *, topic() AS topic FROM '$aws/things/+/shadow/update/accepted' WHERE isUndefined(state.reported.response) = False", actions=[ iot.CfnTopicRule.ActionProperty( lambda_=iot.CfnTopicRule.LambdaActionProperty( function_arn=api_dispense_function.function_arn)) ], ), ) # Allow command_response rule to Invoke the dispense function to reconcile outstanding requests api_dispense_function.add_permission( "AllowIoTCommandResponseRule", principal=iam.ServicePrincipal("iot.amazonaws.com"), source_arn=iot_rule_command_response_dispense.attr_arn, ) # Custom resource to delete workshop users - run to clean up any lingering ones # if the admin user didn't clean up. A lot of dependsOn as users are created with bindings # to other resources props: CustomResourceProps = CustomResourceProps( name=id + "-CR-DeleteParticipantUsers", lambda_directory="./lambda_functions/cr_delete_participant_users", handler="index.main", timeout=30, runtime=lambda_.Runtime.PYTHON_3_7, environment={ # Read user records from UserTable "USER_TABLE": user_db.table_name, # Invoke the api_delete_user function "DELETE_USER_LAMBDA_FUNCTION": api_delete_user_function.function_arn, }, ) delete_participant_users_cr = CustomResourceConstruct( self, "DeleteParticpantUsers", props) # DependsOn the API Delete User Function delete_participant_users_cr.resource.node.add_dependency( api_delete_user_function) # DependsOn the user pool to delete Cognito users delete_participant_users_cr.resource.node.add_dependency(user_pool) # DependsOn the DynamoDB UserTable delete_participant_users_cr.resource.node.add_dependency(user_db) # DependsOn the IoT dispenser and client policies delete_participant_users_cr.resource.node.add_dependency( iot_policy_dispenser_limited) delete_participant_users_cr.resource.node.add_dependency( iot_policy_client) # DependsOn the IoT IAM user group delete_participant_users_cr.resource.node.add_dependency(user_group) # Permissions for function to delete users policy_statement = iam.PolicyStatement() policy_statement.add_actions("dynamodb:*") policy_statement.add_resources( f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}" ) delete_participant_users_cr.add_policy_to_role(policy_statement) policy_statement = iam.PolicyStatement() policy_statement.add_actions("lambda:InvokeFunction") policy_statement.add_resources(api_delete_user_function.function_arn) delete_participant_users_cr.add_policy_to_role(policy_statement)
def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # Image Bucket image_bucket = s3.Bucket(self, IMG_BUCKET_NAME, removal_policy=cdk.RemovalPolicy.DESTROY) cdk.CfnOutput(self, "imageBucket", value=image_bucket.bucket_name) image_bucket.add_cors_rule( allowed_methods=[s3.HttpMethods.GET, s3.HttpMethods.PUT], allowed_origins=["*"], allowed_headers=["*"], max_age=3000, ) # Thumbnail Bucket resized_image_bucket = s3.Bucket( self, RESIZED_IMG_BUCKET_NAME, removal_policy=cdk.RemovalPolicy.DESTROY) cdk.CfnOutput(self, "resizedBucket", value=resized_image_bucket.bucket_name) resized_image_bucket.add_cors_rule( allowed_methods=[s3.HttpMethods.GET, s3.HttpMethods.PUT], allowed_origins=["*"], allowed_headers=["*"], max_age=3000, ) # S3 Static bucket for website code web_bucket = s3.Bucket( self, WEBSITE_BUCKET_NAME, website_index_document="index.html", website_error_document="index.html", removal_policy=cdk.RemovalPolicy.DESTROY, # uncomment this and delete the policy statement below to allow public access to our # static website # public_read_access=true ) web_policy_statement = iam.PolicyStatement( actions=["s3:GetObject"], resources=[web_bucket.arn_for_objects("*")], principals=[iam.AnyPrincipal()], conditions={"IpAddress": { "aws:SourceIp": ["139.138.203.36"] }}, ) web_bucket.add_to_resource_policy(web_policy_statement) cdk.CfnOutput(self, "bucketURL", value=web_bucket.bucket_website_domain_name) # Deploy site contents to S3 Bucket s3_dep.BucketDeployment( self, "DeployWebsite", sources=[s3_dep.Source.asset("./public")], destination_bucket=web_bucket, ) # DynamoDB to store image labels partition_key = dynamodb.Attribute(name="image", type=dynamodb.AttributeType.STRING) table = dynamodb.Table( self, "ImageLabels", partition_key=partition_key, removal_policy=cdk.RemovalPolicy.DESTROY, ) cdk.CfnOutput(self, "ddbTable", value=table.table_name) # Lambda layer for Pillow library layer = lb.LayerVersion( self, "pil", code=lb.Code.from_asset("reklayer"), compatible_runtimes=[lb.Runtime.PYTHON_3_7], license="Apache-2.0", description= "A layer to enable the PIL library in our Rekognition Lambda", ) # Lambda function rek_fn = lb.Function( self, "rekognitionFunction", code=lb.Code.from_asset("rekognitionFunction"), runtime=lb.Runtime.PYTHON_3_7, handler="index.handler", timeout=cdk.Duration.seconds(30), memory_size=1024, layers=[layer], environment={ "TABLE": table.table_name, "BUCKET": image_bucket.bucket_name, "THUMBBUCKET": resized_image_bucket.bucket_name, }, ) image_bucket.grant_read(rek_fn) resized_image_bucket.grant_write(rek_fn) table.grant_write_data(rek_fn) rek_fn.add_to_role_policy( iam.PolicyStatement(effect=iam.Effect.ALLOW, actions=["rekognition:DetectLabels"], resources=["*"])) # Lambda for Synchronous front end serviceFn = lb.Function( self, "serviceFunction", code=lb.Code.from_asset("servicelambda"), runtime=lb.Runtime.PYTHON_3_7, handler="index.handler", environment={ "TABLE": table.table_name, "BUCKET": image_bucket.bucket_name, "RESIZEDBUCKET": resized_image_bucket.bucket_name, }, ) image_bucket.grant_write(serviceFn) resized_image_bucket.grant_write(serviceFn) table.grant_read_write_data(serviceFn) # Cognito User Pool Auth auto_verified_attrs = cognito.AutoVerifiedAttrs(email=True) sign_in_aliases = cognito.SignInAliases(email=True, username=True) user_pool = cognito.UserPool( self, "UserPool", self_sign_up_enabled=True, auto_verify=auto_verified_attrs, sign_in_aliases=sign_in_aliases, ) user_pool_client = cognito.UserPoolClient(self, "UserPoolClient", user_pool=user_pool, generate_secret=False) identity_pool = cognito.CfnIdentityPool( self, "ImageRekognitionIdentityPool", allow_unauthenticated_identities=False, cognito_identity_providers=[{ "clientId": user_pool_client.user_pool_client_id, "providerName": user_pool.user_pool_provider_name, }], ) # API Gateway cors_options = apigw.CorsOptions(allow_origins=apigw.Cors.ALL_ORIGINS, allow_methods=apigw.Cors.ALL_METHODS) api = apigw.LambdaRestApi( self, "imageAPI", default_cors_preflight_options=cors_options, handler=serviceFn, proxy=False, ) auth = apigw.CfnAuthorizer( self, "ApiGatewayAuthorizer", name="customer-authorizer", identity_source="method.request.header.Authorization", provider_arns=[user_pool.user_pool_arn], rest_api_id=api.rest_api_id, # type=apigw.AuthorizationType.COGNITO, type="COGNITO_USER_POOLS", ) assumed_by = iam.FederatedPrincipal( "cognito-identity.amazon.com", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" }, }, assume_role_action="sts:AssumeRoleWithWebIdentity", ) authenticated_role = iam.Role( self, "ImageRekognitionAuthenticatedRole", assumed_by=assumed_by, ) # IAM policy granting users permission to get and put their pictures policy_statement = iam.PolicyStatement( actions=["s3:GetObject", "s3:PutObject"], effect=iam.Effect.ALLOW, resources=[ image_bucket.bucket_arn + "/private/${cognito-identity.amazonaws.com:sub}/*", image_bucket.bucket_arn + "/private/${cognito-identity.amazonaws.com:sub}/", resized_image_bucket.bucket_arn + "/private/${cognito-identity.amazonaws.com:sub}/*", resized_image_bucket.bucket_arn + "/private/${cognito-identity.amazonaws.com:sub}/", ], ) # IAM policy granting users permission to list their pictures list_policy_statement = iam.PolicyStatement( actions=["s3:ListBucket"], effect=iam.Effect.ALLOW, resources=[ image_bucket.bucket_arn, resized_image_bucket.bucket_arn ], conditions={ "StringLike": { "s3:prefix": ["private/${cognito-identity.amazonaws.com:sub}/*"] } }, ) authenticated_role.add_to_policy(policy_statement) authenticated_role.add_to_policy(list_policy_statement) # Attach role to our Identity Pool cognito.CfnIdentityPoolRoleAttachment( self, "IdentityPoolRoleAttachment", identity_pool_id=identity_pool.ref, roles={"authenticated": authenticated_role.role_arn}, ) # Get some outputs from cognito cdk.CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id) cdk.CfnOutput(self, "AppClientId", value=user_pool_client.user_pool_client_id) cdk.CfnOutput(self, "IdentityPoolId", value=identity_pool.ref) # New Amazon API Gateway with AWS Lambda Integration success_response = apigw.IntegrationResponse( status_code="200", response_parameters={ "method.response.header.Access-Control-Allow-Origin": "'*'" }, ) error_response = apigw.IntegrationResponse( selection_pattern="(\n|.)+", status_code="500", response_parameters={ "method.response.header.Access-Control-Allow-Origin": "'*'" }, ) request_template = json.dumps({ "action": "$util.escapeJavaScript($input.params('action'))", "key": "$util.escapeJavaScript($input.params('key'))", }) lambda_integration = apigw.LambdaIntegration( serviceFn, proxy=False, request_parameters={ "integration.request.querystring.action": "method.request.querystring.action", "integration.request.querystring.key": "method.request.querystring.key", }, request_templates={"application/json": request_template}, passthrough_behavior=apigw.PassthroughBehavior.WHEN_NO_TEMPLATES, integration_responses=[success_response, error_response], ) imageAPI = api.root.add_resource("images") success_resp = apigw.MethodResponse( status_code="200", response_parameters={ "method.response.header.Access-Control-Allow-Origin": True }, ) error_resp = apigw.MethodResponse( status_code="500", response_parameters={ "method.response.header.Access-Control-Allow-Origin": True }, ) # GET /images get_method = imageAPI.add_method( "GET", lambda_integration, authorization_type=apigw.AuthorizationType.COGNITO, request_parameters={ "method.request.querystring.action": True, "method.request.querystring.key": True, }, method_responses=[success_resp, error_resp], ) # DELETE /images delete_method = imageAPI.add_method( "DELETE", lambda_integration, authorization_type=apigw.AuthorizationType.COGNITO, request_parameters={ "method.request.querystring.action": True, "method.request.querystring.key": True, }, method_responses=[success_resp, error_resp], ) # Override the authorizer id because it doesn't work when defininting it as a param # in add_method get_method_resource = get_method.node.find_child("Resource") get_method_resource.add_property_override("AuthorizerId", auth.ref) delete_method_resource = delete_method.node.find_child("Resource") delete_method_resource.add_property_override("AuthorizerId", auth.ref) # Building SQS queue and DeadLetter Queue dl_queue = sqs.Queue( self, "ImageDLQueue", queue_name="ImageDLQueue", ) dl_queue_opts = sqs.DeadLetterQueue(max_receive_count=2, queue=dl_queue) queue = sqs.Queue( self, "ImageQueue", queue_name="ImageQueue", visibility_timeout=cdk.Duration.seconds(30), receive_message_wait_time=cdk.Duration.seconds(20), dead_letter_queue=dl_queue_opts, ) # S3 Bucket Create Notification to SQS # Whenever an image is uploaded add it to the queue image_bucket.add_object_created_notification( s3n.SqsDestination(queue), s3.NotificationKeyFilter(prefix="private/"))
def __init__(self, scope: core.Construct, id: str, instance_id: str, contact_flow_id: str, source_phone_number: str, timeout: int, **kwargs): super().__init__(scope, id, **kwargs) web_bucket = _s3.Bucket(self, "StaticWebBucket", website_index_document="index.html", website_error_document="index.html", removal_policy=core.RemovalPolicy.DESTROY, public_read_access=True) core.CfnOutput(self, 'WebBucketUrl', value=web_bucket.bucket_domain_name) web_distribution = _clf.CloudFrontWebDistribution( self, 'StaticWebDistribution', origin_configs=[ _clf.SourceConfiguration( s3_origin_source=_clf.S3OriginConfig( s3_bucket_source=web_bucket), behaviors=[_clf.Behavior(is_default_behavior=True)]) ], viewer_protocol_policy=_clf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS) _s3d.BucketDeployment( self, "S3StaticWebContentDeploymentWithInvalidation", sources=[ _s3d.Source.asset( f"{pathlib.Path(__file__).parent.absolute()}/site-content/build" ) ], destination_bucket=web_bucket, distribution=web_distribution, distribution_paths=["/*"]) file_bucket = _s3.Bucket(self, "FileBucket", removal_policy=core.RemovalPolicy.DESTROY) call_dead_letter_queue = _sqs.Queue(self, "CallDeadLetterQueue", fifo=True, content_based_deduplication=True) call_sqs_queue = _sqs.Queue( self, "CallSqsQueue", fifo=True, content_based_deduplication=True, visibility_timeout=core.Duration.seconds(120), dead_letter_queue=_sqs.DeadLetterQueue( max_receive_count=1, queue=call_dead_letter_queue)) async_call_dead_letter_queue = _sqs.Queue( self, "AsyncCallDeadLetterQueue", fifo=True, content_based_deduplication=True) async_callout_queue = _sqs.Queue( self, "AsyncCalloutQueue", fifo=True, content_based_deduplication=True, visibility_timeout=core.Duration.seconds(120), dead_letter_queue=_sqs.DeadLetterQueue( max_receive_count=1, queue=async_call_dead_letter_queue)) call_job_complete_sns_topic = _sns.Topic( self, "CallJobCompleteSnsTopic", display_name="CallJobCompletion") call_result_table = _dynamodb.Table( self, "CallResultDynamodbTable", billing_mode=_dynamodb.BillingMode.PAY_PER_REQUEST, partition_key=_dynamodb.Attribute( name="task_id", type=_dynamodb.AttributeType.STRING), sort_key=_dynamodb.Attribute(name="receiver_id", type=_dynamodb.AttributeType.STRING), removal_policy=core.RemovalPolicy.DESTROY) callout_record_table = _dynamodb.Table( self, "CallTaskDynamodbTable", billing_mode=_dynamodb.BillingMode.PAY_PER_REQUEST, partition_key=_dynamodb.Attribute( name="task_id", type=_dynamodb.AttributeType.STRING), sort_key=_dynamodb.Attribute(name="created_at", type=_dynamodb.AttributeType.NUMBER), removal_policy=core.RemovalPolicy.DESTROY) callout_record_table.add_global_secondary_index( partition_key=_dynamodb.Attribute( name='call_type', type=_dynamodb.AttributeType.STRING), sort_key=_dynamodb.Attribute(name='created_at', type=_dynamodb.AttributeType.NUMBER), index_name='CallTypeCreatedAtGlobalIndex', projection_type=_dynamodb.ProjectionType.ALL) python_function_layer = _lambda.LayerVersion( self, "LambdaPythonFunctionLayer", code=_lambda.Code.asset("aws_callouts_cdk/layer/_python"), compatible_runtimes=[ _lambda.Runtime.PYTHON_3_7, _lambda.Runtime.PYTHON_3_8 ], license="Available under the MIT-0 license") nodejs_function_layer = _lambda.LayerVersion( self, "LambdaNodeJsFunctionLayer", code=_lambda.Code.asset("aws_callouts_cdk/layer/_nodejs"), compatible_runtimes=[ _lambda.Runtime.NODEJS_10_X, _lambda.Runtime.NODEJS_12_X ], license="Available under the MIT-0 license") global_python_function_arguments = { "code": _lambda.Code.asset("aws_callouts_cdk/src/python"), "layers": [python_function_layer], "runtime": _lambda.Runtime.PYTHON_3_7 } global_nodeje_function_arguments = { "code": _lambda.Code.asset("aws_callouts_cdk/src/nodejs"), "layers": [nodejs_function_layer], "runtime": _lambda.Runtime.NODEJS_12_X } get_callout_job_function = _lambda.Function( self, "GetCalloutJobFunction", handler="get_call_job.lambda_handler", **global_python_function_arguments) get_callout_job_function.add_environment(key="S3Bucket", value=file_bucket.bucket_name) file_bucket.grant_read(get_callout_job_function) callout_function = _lambda.Function(self, "CalloutFunction", handler="send_call.lambda_handler", **global_python_function_arguments) callout_function.add_environment( key="ContactFlowArn", value= f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}/contact-flow/{contact_flow_id}" ) callout_function.add_environment(key="SourcePhoneNumber", value=source_phone_number) callout_function.add_environment(key="ExcelFileBucket", value=file_bucket.bucket_name) callout_function.add_environment(key="AsynCalloutQueueUrl", value=async_callout_queue.queue_url) callout_function.add_to_role_policy(statement=_iam.PolicyStatement( resources=[ f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}/*" ], actions=["connect:StartOutboundVoiceContact"])) callout_function.add_event_source(source=_les.SqsEventSource( queue=async_callout_queue, batch_size=1)) 'arn:aws:connect:751225572132:ap-southeast-2:instance/9d0c7cc5-7d2a-42e4-a3dd-70f402e0d040' file_bucket.grant_read_write(callout_function) response_handler_function = _lambda.Function( self, "ResponseHandlerFunction", handler="response_handler.lambda_handler", **global_python_function_arguments) response_handler_function.add_permission( id="ResponseHandlerFunctionLambdaInvokePermission", principal=_iam.ServicePrincipal(service="connect.amazonaws.com"), action="lambda:InvokeFunction", source_account=self.account, source_arn= f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}" ) send_task_success_function = _lambda.Function( self, "SendTaskSuccessFunction", handler="send_task_success.lambda_handler", **global_python_function_arguments) send_task_success_function.add_permission( id="SendTaskSuccessFunctionLambdaInvokePermission", principal=_iam.ServicePrincipal(service="connect.amazonaws.com"), action="lambda:InvokeFunction", source_account=self.account, source_arn= f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}" ) get_call_result_function = _lambda.Function( self, "GetCallResultFunction", handler="get_call_result.lambda_handler", memory_size=512, **global_python_function_arguments) get_call_result_function.add_environment( key="CallResultDynamoDBTable", value=call_result_table.table_name) get_call_result_function.add_environment(key="S3Bucket", value=file_bucket.bucket_name) call_result_table.grant_read_data(grantee=get_call_result_function) file_bucket.grant_read_write(get_call_result_function) iterator_function = _lambda.Function( self, "IteratorFunction", handler="iterator.lambda_handler", **global_python_function_arguments) iterator_function.add_permission( id="IteratorFunctionLambdaInvokePermission", principal=_iam.ServicePrincipal(service="connect.amazonaws.com"), action="lambda:InvokeFunction", source_account=self.account, source_arn= f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}" ) create_appsync_call_task_function = _lambda.Function( self, "CreateAppSyncCallTaskFunction", handler="create_appsync_call_task.lambda_handler", **global_nodeje_function_arguments) create_appsync_call_task_function.add_environment( key="CallSqsQueueUrl", value=call_sqs_queue.queue_url) create_appsync_call_task_function.add_environment( key="CallRecordTableName", value=callout_record_table.table_name) call_sqs_queue.grant_send_messages(create_appsync_call_task_function) callout_record_table.grant_write_data( create_appsync_call_task_function) create_call_report_record_function = _lambda.Function( self, "CreateCallReportRecordFunction", handler="create_call_report_record.lambda_handler", **global_nodeje_function_arguments) create_excel_call_task_function = _lambda.Function( self, "CreateExcelCallTaskFunction", handler="create_excel_call_task.lambda_handler", **global_python_function_arguments) create_excel_call_task_function.add_environment( key="CallSqsQueueUrl", value=call_sqs_queue.queue_url) call_sqs_queue.grant_send_messages(create_excel_call_task_function) create_excel_call_task_function.add_event_source( source=_les.S3EventSource(bucket=file_bucket, events=[_s3.EventType.OBJECT_CREATED], filters=[ _s3.NotificationKeyFilter( prefix="call_task", suffix=".xlsx") ])) start_callout_flow_function = _lambda.Function( self, "StartCalloutFlowFunction", handler="start_call_out_flow.lambda_handler", reserved_concurrent_executions=1, **global_python_function_arguments) start_callout_flow_function.add_environment( key="CallSqsQueueUrl", value=call_sqs_queue.queue_url) start_callout_flow_function.add_environment( key="ResponseHandlerFunctionArn", value=response_handler_function.function_arn) start_callout_flow_function.add_environment( key="IteratorFunctionArn", value=iterator_function.function_arn) start_callout_flow_function.add_environment( key="SendTaskSuccessFunctionArn", value=send_task_success_function.function_arn) start_callout_flow_function.add_environment( key="S3Bucket", value=file_bucket.bucket_name) start_callout_flow_function.add_event_source( source=_les.SqsEventSource(queue=call_sqs_queue, batch_size=1)) file_bucket.grant_read_write(start_callout_flow_function) call_state_machine_definition = { "Comment": "Reading messages from an SQS queue and iteratively processing each message.", "StartAt": "Start", "States": { "Start": { "Type": "Pass", "Next": "Process Call Messages" }, "Process Call Messages": { "Type": "Map", "Next": "Get Call Result", "InputPath": "$", "ItemsPath": "$", "OutputPath": "$.[0]", "Iterator": { "StartAt": "Get Call out job", "States": { "Get Call out job": { "Type": "Task", "Resource": get_callout_job_function.function_arn, "Next": "Callout with AWS Connect" }, "Callout with AWS Connect": { "Type": "Task", "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken", "TimeoutSeconds": timeout, "Parameters": { "QueueUrl": async_callout_queue.queue_url, "MessageGroupId": "1", "MessageBody": { "Message.$": "$", "TaskToken.$": "$$.Task.Token" } }, "Catch": [{ "ErrorEquals": ["States.Timeout"], "ResultPath": None, "Next": "Call Timeout" }], "Next": "Save call result" }, "Call Timeout": { "Type": "Pass", "ResultPath": None, "Next": "Save call result" }, "Save call result": { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:putItem", "Parameters": { "TableName": call_result_table.table_name, "Item": { "receiver_id": { "S.$": "$.receiver_id" }, "task_id": { "S.$": "$.task_id" }, "username": { "S.$": "$.username" }, "phone_number": { "S.$": "$.phone_number" }, "status": { "S.$": "$.status" }, "answers": { "S.$": "$.answers" }, "error": { "S.$": "$.error" }, "call_at": { "S.$": "$.call_at" } } }, "ResultPath": "$.Result", "OutputPath": "$.task_id", "End": True } } } }, "Get Call Result": { "Type": "Task", "Resource": get_call_result_function.function_arn, "Next": "Create Call Report Record" }, "Create Call Report Record": { "Type": "Task", "Resource": create_call_report_record_function.function_arn, "Next": "Send Completion message to SNS" }, "Send Completion message to SNS": { "Type": "Task", "Resource": "arn:aws:states:::sns:publish", "Parameters": { "TopicArn": call_job_complete_sns_topic.topic_arn, "Message.$": "$" }, "Next": "Finish" }, "Finish": { "Type": "Succeed" } } } callout_state_machine_role = _iam.Role( self, "CalloutStatesExecutionRole", assumed_by=_iam.ServicePrincipal( f"states.{self.region}.amazonaws.com")) callout_state_machine_role.add_to_policy( _iam.PolicyStatement( actions=[ "sqs:SendMessage", "dynamodb:PutItem", "lambda:InvokeFunction", "SNS:Publish" ], resources=[ async_callout_queue.queue_arn, call_result_table.table_arn, get_callout_job_function.function_arn, get_call_result_function.function_arn, call_job_complete_sns_topic.topic_arn, create_appsync_call_task_function.function_arn, create_call_report_record_function.function_arn ])) callout_state_machine = _sfn.CfnStateMachine( self, "CalloutStateMachine", role_arn=callout_state_machine_role.role_arn, definition_string=json.dumps(call_state_machine_definition)) send_task_success_function.add_to_role_policy( _iam.PolicyStatement(actions=["states:SendTaskSuccess"], resources=[callout_state_machine.ref])) start_callout_flow_function.add_environment( key="CalloutStateMachineArn", value=callout_state_machine.ref) start_callout_flow_function.add_to_role_policy( _iam.PolicyStatement(effect=_iam.Effect.ALLOW, resources=[callout_state_machine.ref], actions=['states:StartExecution'])) user_pool = _cognito.UserPool( self, "UserPool", sign_in_type=_cognito.SignInType.USERNAME) user_pool_client = _cognito.UserPoolClient(self, "UserPoolClient", user_pool=user_pool) appsync_api = _appsync.GraphQLApi( self, "AppSyncApi", name="AWSCalloutApi", user_pool_config=_appsync.UserPoolConfig( user_pool=user_pool, default_action=_appsync.UserPoolDefaultAction.ALLOW), log_config=_appsync.LogConfig( field_log_level=_appsync.FieldLogLevel.ALL), schema_definition_file= f"{pathlib.Path(__file__).parent.absolute()}/schema.graphql") callout_record_ddb_ds = appsync_api.add_dynamo_db_data_source( name="CalloutRecordDdb", description="Callout Record DynamoDB Data Source", table=callout_record_table) callout_record_ddb_ds.create_resolver( type_name="Query", field_name="getLatestCallTaskRecords", request_mapping_template=_appsync.MappingTemplate.from_string( '{"version":"2017-02-28","operation":"Query","index":"CallTypeCreatedAtGlobalIndex","query":{"expression":"call_type = :call_type","expressionValues":{":call_type":{"S":"TASK"}}},"scanIndexForward":false,"limit":${ctx.args.limit}}' ), response_mapping_template=_appsync.MappingTemplate. dynamo_db_result_list()) callout_record_ddb_ds.create_resolver( type_name="Query", field_name="getLatestCallReportRecords", request_mapping_template=_appsync.MappingTemplate.from_string( '{"version":"2017-02-28","operation":"Query","index":"CallTypeCreatedAtGlobalIndex","query":{"expression":"call_type = :call_type","expressionValues":{":call_type":{"S":"REPORT"}}},"scanIndexForward":false,"limit":${ctx.args.limit}}' ), response_mapping_template=_appsync.MappingTemplate. dynamo_db_result_list()) callout_record_ddb_ds.create_resolver( type_name="Mutation", field_name="createCallReport", request_mapping_template=_appsync.MappingTemplate.from_string( '{"version":"2017-02-28","operation":"PutItem","key":{"task_id":{"S":"${ctx.args.report.task_id}"},"created_at":{"N":"${ctx.args.report.created_at}"}},"attributeValues":$util.dynamodb.toMapValuesJson($ctx.args.report)}' ), response_mapping_template=_appsync.MappingTemplate. dynamo_db_result_item()) call_task_lambda_ds = appsync_api.add_lambda_data_source( name="CallTaskLambda", description="Call Task Lambda Data Source", lambda_function=create_appsync_call_task_function) call_task_lambda_ds.create_resolver( type_name="Mutation", field_name="createCallTask", request_mapping_template=_appsync.MappingTemplate.lambda_request( "$utils.toJson($ctx.args)"), response_mapping_template=_appsync.MappingTemplate.lambda_result()) create_call_report_record_function.add_environment( value=appsync_api.graph_ql_url, key="AppSyncGraphQlApiUrl") create_call_report_record_function.add_to_role_policy( statement=_iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=['appsync:GraphQL'], resources=[ f"{appsync_api.arn}/types/Mutation/fields/createCallReport" ])) core.CfnOutput(self, id="OutputCallSqsQueue", value=call_sqs_queue.queue_arn) core.CfnOutput(self, id="OutputCallJobCompletionSNSTopic", value=call_job_complete_sns_topic.topic_arn) core.CfnOutput(self, id="OutputExcelFileS3Bucket", value=file_bucket.bucket_name) core.CfnOutput(self, id="OutputStaticWebS3Bucket", value=web_bucket.bucket_name) core.CfnOutput(self, id="OutputStaticWebUrl", value=web_bucket.bucket_website_url) identity_pool = _cognito.CfnIdentityPool( self, "IdentityPool", allow_unauthenticated_identities=True, cognito_identity_providers=[ _cognito.CfnIdentityPool.CognitoIdentityProviderProperty( provider_name=user_pool.user_pool_provider_name, client_id=user_pool_client.user_pool_client_id) ]) identity_pool_unauthorized_role = _iam.Role( self, 'IdentityPoolUnAuthorizedRole', assumed_by=_iam.FederatedPrincipal( federated="cognito-identity.amazonaws.com", assume_role_action="sts:AssumeRoleWithWebIdentity", conditions={ "StringEquals": { "cognito-identity.amazonaws.com:aud": identity_pool.ref }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" } })) identity_pool_unauthorized_role.add_to_policy( _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=["appsync:GraphQL"], resources=[ f"{appsync_api.arn}/types/*", # f"{appsync_api.arn}/types/Query/fields/getLatestCallTaskRecords", # f"{appsync_api.arn}/types/Query/fields/getLatestCallReportRecords", # f"{appsync_api.arn}/types/Mutation/fields/createCallRecord", # f"{appsync_api.arn}/types/Subscription/fields/createCallTask", # f"{appsync_api.arn}/types/Subscription/fields/createCallReport" ])) _cognito.CfnIdentityPoolRoleAttachment( self, "CognitoIdentityPoolRoleAttachment", identity_pool_id=identity_pool.ref, roles={ "unauthenticated": identity_pool_unauthorized_role.role_arn }) core.CfnOutput(self, id="UserPoolId", value=user_pool.user_pool_id) core.CfnOutput(self, id="UserPoolClientId", value=user_pool_client.user_pool_client_id) core.CfnOutput(self, id="IdentityPoolId", value=identity_pool.ref)
def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None: """ initialize function for CDK """ super().__init__(scope, construct_id, **kwargs) # ------------------------------- # S3 Bucket for Manifests # ------------------------------- qs_gov_bucket = s3.Bucket( self, id=f"{cf.PROJECT}-Bucket", ) bucket_name = qs_gov_bucket.bucket_name # ------------------------------- # IAM # ------------------------------- list_roles_policy = iam.ManagedPolicy( self, id=f"{cf.PROJECT}-ListRolesPolicy", managed_policy_name=f"{cf.PROJECT}-ListRolesPolicy", description=None, path="/", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=["iam:ListRoles", "iam:ListAccountAliases"], ) ], ) federated_quicksight_policy = iam.ManagedPolicy( self, id=f"{cf.PROJECT}-FederatedQuickSightPolicy", managed_policy_name=f"{cf.PROJECT}-FederatedQuickSightPolicy", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:iam::{cf.ACCOUNT}:saml-provider/{cf.OKTA_IDP_NAME}" ], actions=["sts:AssumeRoleWithSAML"], conditions={ "StringEquals": { "saml:aud": "https://signin.aws.amazon.com/saml" } }, ) ], ) okta_federated_principal = iam.FederatedPrincipal( federated= f"arn:aws:iam::{cf.ACCOUNT}:saml-provider/{cf.OKTA_IDP_NAME}", assume_role_action="sts:AssumeRoleWithSAML", conditions={ "StringEquals": { "SAML:aud": "https://signin.aws.amazon.com/saml" } }, ) federated_quicksight_role = iam.Role( self, id=f"{cf.PROJECT}-{cf.QS_FEDERATED_ROLE_NAME}", role_name=f"{cf.QS_FEDERATED_ROLE_NAME}", assumed_by=okta_federated_principal, description= "Allow Okta to Federate Login & User Creation to QuickSight", managed_policies=[federated_quicksight_policy], ) iam.User( self, id=f"{cf.PROJECT}-OktaSSOUser", user_name=f"{cf.PROJECT}-OktaSSOUser", managed_policies=[list_roles_policy], ) # ------------------------------- # Lambda Layers # ------------------------------- path_src = os.path.join(cf.PATH_SRC, "") sp.call(["make", "bundle"], cwd=path_src) requests_layer = _lambda.LayerVersion( self, f"{cf.PROJECT}-requests-layer", code=_lambda.Code.from_asset(os.path.join(path_src, "requests.zip"))) sp.call(["make", "clean"], cwd=path_src) # ------------------------------- # Lambda Functions # ------------------------------- # iam role for Lambdas qs_governance_policy = iam.ManagedPolicy( self, id=f"{cf.PROJECT}-LambdaPolicy", managed_policy_name=f"{cf.PROJECT}-LambdaPolicy", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=["ses:SendEmail", "ses:SendRawEmail"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:secretsmanager:{cf.REGION}:{cf.ACCOUNT}:secret:{cf.OKTA_SECRET_ID}*", f"arn:aws:secretsmanager:{cf.REGION}:{cf.ACCOUNT}:secret:{cf.SLACK_SECRET_ID}*" ], actions=[ "secretsmanager:GetSecretValue", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:iam::{cf.ACCOUNT}:policy/{cf.QS_PREFIX}*" ], actions=[ "iam:CreatePolicy", "iam:GetPolicy", "iam:DeletePolicy", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=["iam:ListPolicies"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=[ "glue:GetDatabase", "glue:GetDatabases", "glue:GetTable", "glue:GetPartitions", "glue:GetPartition", "glue:GetTables", ], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=["quicksight:*", "ds:*"], ), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:s3:::{bucket_name}/*", f"arn:aws:s3:::{bucket_name}*" ], actions=["s3:*"], ), ], ) qs_governance_role = iam.Role( self, id=f"{cf.PROJECT}-QuickSightPermissionMappingRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), managed_policies=[ iam.ManagedPolicy.from_aws_managed_policy_name( "service-role/AWSLambdaBasicExecutionRole"), qs_governance_policy, ], ) # Lambdas get_okta_information_lambda = _lambda.Function( self, id=f"{cf.PROJECT}-GetOktaInfo", function_name=f"{cf.PROJECT}-GetOktaInformation", handler="get_okta_information.handler", role=qs_governance_role, runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")), environment={ "OKTA_SECRET_ID": cf.OKTA_SECRET_ID, "QS_PREFIX": cf.QS_PREFIX, "QS_GOVERNANCE_BUCKET": bucket_name, "QS_FEDERATED_ROLE_NAME": f"{cf.QS_FEDERATED_ROLE_NAME}", "S3_PREFIX_USERS": cf.S3_PREFIX_USERS, "SLACK_SECRET_ID": cf.SLACK_SECRET_ID }, memory_size=256, timeout=core.Duration.seconds(180), layers=[requests_layer], ) # Lamda Okta to QuickSight Mappers qs_user_governance_lambda = _lambda.Function( self, id=f"{cf.PROJECT}-QSUserGovernance", function_name=f"{cf.PROJECT}-QSUserGovernance", handler="qs_user_governance.handler", role=qs_governance_role, runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")), environment={ "QS_FEDERATED_ROLE_NAME": f"{cf.QS_FEDERATED_ROLE_NAME}", "QS_PREFIX": cf.QS_PREFIX, "QS_ADMIN_OKTA_GROUP": cf.QS_ADMIN_OKTA_GROUP, "QS_AUTHOR_OKTA_GROUP": cf.QS_AUTHOR_OKTA_GROUP, "QS_READER_OKTA_GROUP": cf.QS_READER_OKTA_GROUP, "SLACK_SECRET_ID": cf.SLACK_SECRET_ID }, memory_size=256, timeout=core.Duration.seconds(180), layers=[requests_layer], ) qs_asset_governance_lambda = _lambda.Function( self, id=f"{cf.PROJECT}-QSAssetGovernance", function_name=f"{cf.PROJECT}-QSAssetGovernance", handler="qs_asset_governance.handler", role=qs_governance_role, runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")), memory_size=256, timeout=core.Duration.seconds(180), layers=[requests_layer], environment={ "QS_GOVERNANCE_BUCKET": f"{bucket_name}", "S3_PREFIX_USERS": cf.S3_PREFIX_USERS, "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES, "STAGE": cf.DEPLOYMENT_STAGE, "SLACK_SECRET_ID": cf.SLACK_SECRET_ID, "VERIFIED_EMAIL": cf.VERIFIED_EMAIL }) qs_policy_governance_lambda = _lambda.Function( self, id=f"{cf.PROJECT}-QSPolicyGovernance", function_name=f"{cf.PROJECT}-QSPolicyGovernance", handler="qs_policy_governance.handler", role=qs_governance_role, runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")), environment={ "QS_ADMIN_OKTA_GROUP": cf.QS_ADMIN_OKTA_GROUP, "QS_AUTHOR_OKTA_GROUP": cf.QS_AUTHOR_OKTA_GROUP, "QS_AUTHOR_BASE_POLICY": cf.QS_AUTHOR_BASE_POLICY, "QS_PREFIX": cf.QS_PREFIX, "QS_GOVERNANCE_BUCKET": f"{bucket_name}", "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES, "QS_SUPERUSER": cf.QS_SUPERUSER, "DEPLOYMENT_STAGE": cf.DEPLOYMENT_STAGE, "SLACK_SECRET_ID": cf.SLACK_SECRET_ID }, memory_size=256, timeout=core.Duration.seconds(180), layers=[requests_layer]) qs_resource_cleanup_lambda = _lambda.Function( self, id=f"{cf.PROJECT}-QSResourceCleanup", function_name=f"{cf.PROJECT}-QSResourceCleanup", handler="qs_resource_cleanup.handler", role=qs_governance_role, runtime=_lambda.Runtime.PYTHON_3_8, code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")), memory_size=256, timeout=core.Duration.seconds(180), layers=[requests_layer], environment={ "QS_PREFIX": cf.QS_PREFIX, "QS_SUPERUSER": cf.QS_SUPERUSER, "QS_GOVERNANCE_BUCKET": f"{bucket_name}", "S3_PREFIX_ASSETS": cf.S3_PREFIX_ASSETS, "S3_PREFIX_USERS": cf.S3_PREFIX_USERS, "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES, "SLACK_SECRET_ID": cf.SLACK_SECRET_ID }) # ------------------------------- # S3 Event Triggers # ------------------------------- qs_user_governance_lambda.add_event_source( lambda_event_sources.S3EventSource( bucket=qs_gov_bucket, events=[s3.EventType.OBJECT_CREATED], filters=[s3.NotificationKeyFilter(prefix=cf.S3_PREFIX_USERS)], )) qs_policy_governance_lambda.add_event_source( lambda_event_sources.S3EventSource( bucket=qs_gov_bucket, events=[s3.EventType.OBJECT_CREATED], filters=[ s3.NotificationKeyFilter(prefix=cf.S3_PREFIX_POLICIES) ], )) # ------------------------------- # CloudWatch Event Rules (30 minutes) # ------------------------------- lambda_schedule = events.Schedule.rate(core.Duration.minutes(30)) get_okta_info_target = events_targets.LambdaFunction( handler=get_okta_information_lambda) events.Rule( self, id=f"{cf.PROJECT}-GetOktaInfoScheduledEvent", description="The scheduled CloudWatch event trigger for the Lambda", enabled=True, schedule=lambda_schedule, targets=[get_okta_info_target], ) qs_asset_gov_target = events_targets.LambdaFunction( handler=qs_asset_governance_lambda) events.Rule( self, id=f"{cf.PROJECT}-QSAssetGovScheduledEvent", description="The scheduled CloudWatch event trigger for the Lambda", enabled=True, schedule=lambda_schedule, targets=[qs_asset_gov_target], ) qs_cleanup_target = events_targets.LambdaFunction( handler=qs_resource_cleanup_lambda) events.Rule( self, id=f"{cf.PROJECT}-QSCleanupScheduledEvent", description="The scheduled CloudWatch event trigger for the Lambda", enabled=True, schedule=lambda_schedule, targets=[qs_cleanup_target], ) # ------------------------------- # Athena WorkGroup # ------------------------------- workgroup_encryption = athena.CfnWorkGroup.EncryptionConfigurationProperty( encryption_option='SSE_S3') workgroup_output = athena.CfnWorkGroup.ResultConfigurationProperty( output_location="s3://" + cf.ATHENA_OUTPUT_LOC, encryption_configuration=workgroup_encryption) quicksight_athena_workgroup = athena.CfnWorkGroup( self, id=f"{cf.PROJECT}-workgroup", name=f"{cf.QS_PREFIX}-workgroup", description="workgroup for QuickSight Data Source operations", recursive_delete_option=True, work_group_configuration=athena.CfnWorkGroup. WorkGroupConfigurationProperty( result_configuration=workgroup_output)) workgroup_name = quicksight_athena_workgroup.name # ------------------------------- # S3 Object Deployments # ------------------------------- policy_manifest_deploy = s3_deploy.BucketDeployment( self, id=f"{cf.PROJECT}-PolicyManifestDeploy", sources=[ s3_deploy.Source.asset( os.path.join(cf.PATH_ROOT, "qs_config/policies")) ], destination_bucket=qs_gov_bucket, destination_key_prefix=cf.S3_PREFIX_POLICIES) # ------------------------------- # QuickSight IAM Policy Assignments # ------------------------------- qs_author_base_policy = iam.ManagedPolicy( self, id=f"{cf.PROJECT}-{cf.QS_AUTHOR_BASE_POLICY}", managed_policy_name=f"{cf.QS_AUTHOR_BASE_POLICY}", statements=[ iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:athena:{cf.REGION}:{cf.ACCOUNT}:workgroup/{workgroup_name}" ], actions=[ "athena:GetNamedQuery", "athena:CancelQueryExecution", "athena:CreateNamedQuery", "athena:DeleteNamedQuery", "athena:StartQueryExecution", "athena:StopQueryExecution", "athena:GetWorkGroup", "athena:GetNamedQuery", "athena:GetQueryResults", "athena:GetQueryExecution", "athena:BatchGetQueryExecution", "athena:BatchGetNamedQuery", "athena:ListNamedQueries", "athena:ListQueryExecutions", "athena:GetQueryResultsStream" ]), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=[ "athena:ListWorkGroups", "athena:ListDataCatalogs", "athena:StartQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResultsStream", "athena:ListTableMetadata", "athena:GetTableMetadata" ]), iam.PolicyStatement(effect=iam.Effect.ALLOW, resources=[ "arn:aws:s3:::ast-datalake-*", "arn:aws:s3:::aws-athena-query-*", f"arn:aws:s3:::{bucket_name}*", f"arn:aws:s3:::{cf.ATHENA_OUTPUT_LOC}*" ], actions=[ "s3:List*", "s3:Get*", ]), iam.PolicyStatement(effect=iam.Effect.ALLOW, resources=["*"], actions=["s3:ListAllMyBuckets"]), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ "arn:aws:s3:::aws-athena-query-*", f"arn:aws:s3:::{bucket_name}/{cf.QS_PREFIX}/athena-results*" ], actions=["s3:Put*"]), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=[ f"arn:aws:athena:{cf.REGION}:{cf.ACCOUNT}:datacatalog/AwsDataCatalog" ], actions=["athena:ListDatabases"]), iam.PolicyStatement( effect=iam.Effect.ALLOW, resources=["*"], actions=["kms:Decrypt", "kms:GenerateDataKey"]), ], )