AWSTemplateFormatVersion: 2010-09-09 Description: This stack provisions resources necessary to use this AWS account with Service Workbench. Parameters: EnableAppStream: Type: String AllowedValues: [true, false] Description: Onboard this account to support AppStream EnableFlowLogs: Type: String AllowedValues: [true, false] Description: Enable flow logs on VPCs and Subnets created on this account Namespace: Type: String Description: An environment name that will be prefixed to resource names CentralAccountId: Type: String Description: The account id of the main AWS account where the solution is deployed. ExternalId: Type: String Description: A unique ID used to identify this account VpcCidr: Description: Please enter the IP range (CIDR notation) for this VPC Type: String Default: 10.0.0.0/16 ApiHandlerArn: Type: String Description: The arn of apiHandler role WorkflowRoleArn: Type: String Description: The arn of workflowRunner role # Generous subnet allocation of 8192 addresses (ie room for a few concurrent EMR clusters) # Range from 10.0.0.0 to 10.0.31.255 PublicSubnetCidr: Description: Please enter the IP range (CIDR notation) for the public subnet. This value is only required if AppStream is disabled. Type: String Default: 10.0.0.0/19 LaunchConstraintRolePrefix: Description: Role name prefix to use when creating a launch constraint role in the on-boarded account Type: String Default: '*' LaunchConstraintPolicyPrefix: Description: Customer managed policy name prefix to use when creating a launch constraint role in the on-boarded account Type: String Default: '*' #------------AppStream Parameters Below------- # Range from 10.0.32.0 to 10.0.63.255 AppStreamSubnetCidr: Description: Please enter the IP range (CIDR notation) for the the AppStream subnet. This value is only used if AppStream is enabled. Type: String Default: 10.0.32.0/19 # Range from 10.0.64.0 to 10.0.95.255 WorkspaceSubnetCidr: Description: Please enter the IP range (CIDR notation) for the Workspace subnet. This value is only used if AppStream is enabled. Type: String Default: 10.0.64.0/19 AppStreamFleetDesiredInstances: Description: The desired number of streaming instances. Type: Number Default: 2 AppStreamDisconnectTimeoutSeconds: Description: The amount of time that a streaming session remains active after users disconnect. Type: Number Default: 60 AppStreamIdleDisconnectTimeoutSeconds: Description: The amount of time that users can be idle (inactive) before they are disconnected from their streaming session Type: Number Default: 600 AppStreamMaxUserDurationSeconds: Description: The maximum amount of time that a streaming session can remain active, in seconds. Type: Number Default: 86400 AppStreamImageName: Description: The name of the image used to create the fleet. Type: String AppStreamInstanceType: Description: The instance type to use when launching fleet instances. List of images available at https://aws.amazon.com/appstream2/pricing/ Type: String AppStreamFleetType: Description: The fleet type Type: String AllowedValues: [ALWAYS_ON, ON_DEMAND] Default: ON_DEMAND DomainName: Description: Optional custom Domain name to be created in Route53 Type: String Default: '' EnableAmiSharing: Type: String AllowedValues: [true, false] Description: Use appstream image from the central devops account Default: false DevopsAccountId: Type: String Description: The account id of the central AWS account where the appstream image is created. Default: '' StudyDataBucketName: Type: String Description: The name of the S3 bucket that hosts the data for internal studies in the main account. The name is likely ----studydata. Supply to grant write access to this bucket. Default: '' EgressStoreBucketName: Type: String Description: The name of the S3 bucket that hosts the egress store for environments in the main account. The name is likely ----egress-store. Supply to grant write access to this bucket. Default: '' # Note: While adding additional CIDRs/Subnets ensure they don't overlap with those used by RStudio ALB (10.0.96.0 to 10.0.97.255) Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Shared Configuration Parameters: - Namespace - Label: default: Account Configuration Parameters: - CentralAccountId - ExternalId - Label: default: Deployment Configuration Parameters: - VpcCidr - PublicSubnetCidr # Resources linked to AppStream conditions are evaluated further prior to getting sent to CloudFormation # Take a look at addons/addon-base-raas/packages/base-raas-services/lib/cfn-templates/cfn-template-service.getTemplate() Conditions: isAppStream: !Equals - !Ref EnableAppStream - true isNotAppStream: !Not [Condition: isAppStream] hasCustomDomain: !Not [!Equals [!Ref 'DomainName', '']] isAppStreamAndCustomDomain: !And - !Not [!Equals [!Ref 'DomainName', '']] - !Condition isAppStream enableFlowLogs: !Equals [!Ref EnableFlowLogs, true] enableFlowLogsNonAppStream: !And [Condition: isNotAppStream, Condition: enableFlowLogs] enableFlowLogsWithAppStream: !And [Condition: isAppStream, Condition: enableFlowLogs] amiSharingEnabled: !Equals - !Ref EnableAmiSharing - true Resources: Route53HostedZone: Type: AWS::Route53::HostedZone Condition: isAppStreamAndCustomDomain Properties: Name: !Ref DomainName VPCs: - VPCId: !Ref VPC VPCRegion: !Ref 'AWS::Region' # A role used for launching environments using AWS Service Catalog # This is the role that code (ApiHandlerLambda and WorkflowLoopRunnerLambda) in central account # assumes before performing any AWS Service Catalog interactions in this account (the on-boarded account) # for launching environments. # Equivalent role for central account is created by 'main/solution/backend/config/infra/cloudformation.yml' CrossAccountRoleEnvMgmt: Type: 'AWS::IAM::Role' Properties: RoleName: !Join ['-', [Ref: Namespace, 'xacc-env-mgmt']] Path: '/' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: AWS: - !Join [':', ['arn:aws:iam:', Ref: CentralAccountId, 'root']] - !Ref ApiHandlerArn - !Ref WorkflowRoleArn Action: - 'sts:AssumeRole' Condition: StringEquals: sts:ExternalId: !Ref ExternalId ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSServiceCatalogAdminFullAccess Policies: - PolicyName: ec2-access PolicyDocument: Statement: Effect: Allow Action: - ec2:CreateKeyPair - ec2:DeleteKeyPair - ec2:GetPasswordData - ec2:ModifyImageAttribute - ec2:DescribeImages - ec2:DescribeInstances - ec2:DescribeSecurityGroups - ec2:RevokeSecurityGroup* - ec2:AuthorizeSecurityGroup* - ec2-instance-connect:SendSSHPublicKey Resource: '*' - PolicyName: cfn-access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:GetTemplate Resource: 'arn:aws:cloudformation:*:*:stack/SC-*/*' - PolicyName: appstream-access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - appstream:ListAssociatedFleets - appStream:CreateStreamingURL Resource: - !Sub 'arn:${AWS::Partition}:appstream:${AWS::Region}:${AWS::AccountId}:stack/*' - !Sub 'arn:${AWS::Partition}:appstream:${AWS::Region}:${AWS::AccountId}:fleet/*' - PolicyName: ssm-access PolicyDocument: Statement: Effect: Allow Action: - ssm:PutParameter - ssm:GetParameter - ssm:GetParameters - ssm:DeleteParameter Resource: - !Sub 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*/sc-environments/*' - PolicyName: s3-access PolicyDocument: Statement: Effect: Allow Action: - s3:GetObject Resource: - 'arn:aws:s3:::cf-templates-*/*' - PolicyName: s3-upload-access PolicyDocument: Statement: Effect: Allow Action: - s3:PutObject* Resource: - !Sub 'arn:aws:s3:::${Namespace}*/*' - PolicyName: sts-assume-create-presign PolicyDocument: Statement: Effect: Allow Action: - sts:AssumeRole Resource: - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*presigned-url-sagemaker-notebook-role' - PolicyName: sagemaker-access PolicyDocument: Statement: Effect: Allow Action: - sagemaker:CreatePresignedNotebookInstanceUrl - sagemaker:ListNotebookInstances Resource: '*' - PolicyName: iam-role-access PolicyDocument: Statement: - Effect: Allow Action: - iam:CreateRole - iam:TagRole - iam:GetRolePolicy - iam:PutRolePolicy - iam:DeleteRolePolicy - iam:ListRolePolicies - iam:ListAttachedRolePolicies - iam:UpdateAssumeRolePolicy - iam:UpdateRoleDescription - iam:AttachRolePolicy - iam:DetachRolePolicy Resource: - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${LaunchConstraintRolePrefix}LaunchConstraint' - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*presigned-url-sagemaker-notebook-role' - PolicyName: cloudwatch-access-policy PolicyDocument: Statement: Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${Namespace}-*:*' - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${Namespace}-*:log-stream:*' - !If - isAppStreamAndCustomDomain - PolicyName: route53-access PolicyDocument: Statement: - Effect: Allow Action: - route53:ChangeResourceRecordSets Resource: - !Sub 'arn:aws:route53:::hostedzone/${Route53HostedZone}' - !Ref 'AWS::NoValue' PermissionsBoundary: !Ref CrossAccountEnvMgmtPermissionsBoundary CrossAccountEnvMgmtPermissionsBoundary: Type: AWS::IAM::ManagedPolicy Properties: Description: Permission boundary for cross account EnvMgmt role PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*presigned-url-sagemaker-notebook-role' - Effect: Allow Action: - s3:* - cloudformation:* - sagemaker:* - ec2:* - ssm:* - config:* - servicecatalog:* - ec2-instance-connect:* Resource: '*' - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${Namespace}-*:*' - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${Namespace}-*:log-stream:*' - Effect: Allow Action: - iam:PassRole Resource: '*' Condition: StringEquals: iam:PassedToService: 'servicecatalog.amazonaws.com' - Effect: Allow Action: - iam:CreateRole - iam:TagRole - iam:GetRolePolicy - iam:PutRolePolicy - iam:DeleteRolePolicy - iam:ListRolePolicies - iam:ListAttachedRolePolicies - iam:UpdateAssumeRolePolicy - iam:UpdateRoleDescription - iam:AttachRolePolicy - iam:DetachRolePolicy Resource: - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${LaunchConstraintRolePrefix}' - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*presigned-url-sagemaker-notebook-role' - Effect: Allow Action: - appstream:ListAssociatedFleets - appStream:CreateStreamingURL Resource: - '*' - Effect: Allow Action: - iam:CreatePolicy - iam:GetPolicy - iam:GetPolicyVersion - iam:ListPolicyVersions - iam:DeletePolicy - iam:DeletePolicyVersion Resource: - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${LaunchConstraintPolicyPrefix}' - Effect: Allow Action: - iam:GetGroup - iam:GetRole - iam:GetUser - iam:ListGroups - iam:ListRoles - iam:ListUsers Resource: '*' # These non-mutating IAM actions cover the permissions in managed policy AWSServiceCatalogAdminFullAccess - !If - isAppStreamAndCustomDomain - Effect: Allow Action: - route53:ChangeResourceRecordSets Resource: - !Sub 'arn:aws:route53:::hostedzone/${Route53HostedZone}' - !Ref 'AWS::NoValue' PolicyCrossAccountExecution: Type: AWS::IAM::ManagedPolicy Properties: Description: Allows main account to perform critical analytics on workspaces provisioned in member accounts PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:CreateStack - cloudformation:DeleteStack - cloudformation:DescribeStacks - cloudformation:DescribeStackEvents Resource: - !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/SC-*' - !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/analysis-*' # ALB stack used this pattern - Effect: Allow Action: - sagemaker:CreatePresignedNotebookInstanceUrl - sagemaker:StartNotebookInstance - sagemaker:StopNotebookInstance - sagemaker:DescribeNotebookInstance Resource: !Sub 'arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:notebook-instance/?asic?otebook?nstance-*' - Effect: Allow Action: - sagemaker:ListNotebookInstances Resource: '*' # For the actions listed above IAM does not support resource-level permissions and requires all resources to be chosen - Effect: Allow Action: - iam:GetRole - iam:TagRole - iam:GetRolePolicy - iam:DeleteRolePolicy - iam:DeleteRole - iam:PassRole - iam:PutRolePolicy Resource: !Sub 'arn:aws:iam::${AWS::AccountId}:role/analysis-*' - Effect: Allow Action: - ce:GetCostAndUsage Resource: '*' # For the actions listed above IAM does not support resource-level permissions and requires all resources to be chosen - Effect: Allow Action: - budgets:ViewBudget - budgets:ModifyBudget Resource: !Sub 'arn:aws:budgets::${AWS::AccountId}:budget/service-workbench-system-generated*' - Effect: Allow Action: - ec2:StartInstances - ec2:StopInstances Resource: !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*' - Effect: Allow Action: - ec2:DescribeInstanceStatus - ec2:DescribeInstances - ec2:DescribeRouteTables - ec2:AssociateRouteTable - ec2:DisassociateRouteTable Resource: '*' # For the actions listed above IAM does not support resource-level permissions and requires all resources to be chosen - Effect: Allow Action: - ec2:DescribeSubnets - ec2:DescribeVpcs - ec2:DescribeNetworkInterfaces - ec2:DescribeSecurityGroups - ec2:DescribeAvailabilityZones - ec2:DescribeAccountAttributes - ec2:CreateTags - ec2:DeleteTags - ec2:DescribeInternetGateways - ec2:DescribeVpcEndpoints - ec2:ModifyVpcEndpoint Resource: '*' # For the actions listed above IAM does not support resource-level permissions and requires all resources to be chosen - Effect: Allow Action: - ssm:GetParameter Resource: - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*' - Effect: Allow Action: - ec2:CreateSubnet - ec2:DeleteSubnet - ec2:ModifySubnetAttribute - ec2:AssociateSubnetCidrBlock - ec2:DisassociateSubnetCidrBlock Resource: - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/*' - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/*' - Effect: Allow Action: - ec2:CreateSecurityGroup - ec2:DeleteSecurityGroup - ec2:AuthorizeSecurityGroup* - ec2:RevokeSecurityGroup* Resource: - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/*' - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*' - Effect: Allow Action: - elasticloadbalancing:DescribeLoadBalancers - elasticloadbalancing:DescribeListeners - elasticloadbalancing:DescribeRules - elasticloadbalancing:AddTags - elasticloadbalancing:RemoveTags - elasticloadbalancing:ModifyLoadBalancerAttributes Resource: '*' # For the actions listed above IAM does not support resource-level permissions and requires all resources to be chosen - Effect: Allow Action: - elasticloadbalancing:CreateLoadBalancer - elasticloadbalancing:DeleteLoadBalancer - elasticloadbalancing:SetSecurityGroups - elasticloadbalancing:SetSubnets Resource: - !Sub 'arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/app/analysis-*' - Effect: Allow Action: - elasticloadbalancing:CreateListener - elasticloadbalancing:DeleteListener - elasticloadbalancing:AddListenerCertificates - elasticloadbalancing:RemoveListenerCertificates Resource: - !Sub 'arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/app/analysis-*' - !Sub 'arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:listener/app/analysis-*' - Effect: Allow Action: - elasticloadbalancing:CreateRule - elasticloadbalancing:DeleteRule - elasticloadbalancing:ModifyRule Resource: - !Sub 'arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:listener/app/analysis-*' - !Sub 'arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:listener-rule/app/analysis-*' - Effect: Allow Action: - iam:CreateServiceLinkedRole Resource: - !Sub 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing' CrossAccountExecutionRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join ['-', [Ref: Namespace, 'cross-account-role']] Path: '/' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: AWS: - !Join [':', ['arn:aws:iam:', Ref: CentralAccountId, 'root']] - !Ref ApiHandlerArn - !Ref WorkflowRoleArn Action: - 'sts:AssumeRole' Condition: StringEquals: sts:ExternalId: !Ref ExternalId ManagedPolicyArns: - !Ref PolicyCrossAccountExecution PermissionsBoundary: !Ref PolicyCrossAccountExecution CfnStatusRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join ['-', [Ref: Namespace, 'cfn-status-role']] Path: '/' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: AWS: - !Join [':', ['arn:aws:iam:', Ref: CentralAccountId, 'root']] Action: - 'sts:AssumeRole' Condition: StringEquals: sts:ExternalId: !Ref ExternalId ManagedPolicyArns: - !Ref PolicyCfnStatus PermissionsBoundary: !Ref PolicyCfnStatus PolicyCfnStatus: Type: AWS::IAM::ManagedPolicy Properties: Description: Allows main account to onboard and check status of aws accounts PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:GetTemplate Resource: !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/initial-stack*' # This role is used by `infrastructure-tests` InfrastructureTestRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Join ['-', [Ref: Namespace, 'infrastructure-test-role']] Path: '/' AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: AWS: - !Join [':', ['arn:aws:iam:', Ref: CentralAccountId, 'root']] Action: - 'sts:AssumeRole' Condition: StringEquals: sts:ExternalId: !Ref ExternalId ManagedPolicyArns: - !Ref PolicyInfrastructureTest PermissionsBoundary: !Ref PolicyInfrastructureTest PolicyInfrastructureTest: Type: AWS::IAM::ManagedPolicy Properties: Description: Allows infrastructure tests to access hosting acccount CFN resources PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:DescribeStackResources Resource: !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/initial-stack*' - Effect: Allow Action: - ec2:DescribeSubnets - ec2:DescribeSecurityGroups - ec2:DescribeRouteTables Resource: '*' # VPC for launching EMR clusters into # Just one AZ as we're aiming for transient low-cost clusters rather than HA VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub ${Namespace} vpc FlowLogVPC: Type: AWS::EC2::FlowLog Condition: enableFlowLogs Properties: ResourceId: !Ref VPC ResourceType: VPC TrafficType: ACCEPT DeliverLogsPermissionArn: !GetAtt CrossAccountRoleEnvMgmt.Arn LogGroupName: !Ref FlowLogCloudwatchGroup LogFormat: '${version} ${vpc-id} ${subnet-id} ${instance-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}' Tags: - Key: Name Value: FlowLogForVPC - Key: Purpose Value: AcceptTraffic InternetGateway: Type: AWS::EC2::InternetGateway Condition: isNotAppStream Properties: Tags: - Key: Name Value: !Sub ${Namespace} igw InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Condition: isNotAppStream Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC PublicSubnet1: Type: AWS::EC2::Subnet Condition: isNotAppStream Properties: VpcId: !Ref VPC AvailabilityZone: !Select [0, !GetAZs ''] CidrBlock: !Ref PublicSubnetCidr MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${Namespace} public subnet 1 FlowLogPublicSubnet1: Type: AWS::EC2::FlowLog Condition: enableFlowLogsNonAppStream Properties: ResourceId: !Ref PublicSubnet1 ResourceType: Subnet TrafficType: ACCEPT DeliverLogsPermissionArn: !GetAtt CrossAccountRoleEnvMgmt.Arn LogGroupName: !Ref FlowLogCloudwatchGroup LogFormat: '${version} ${vpc-id} ${subnet-id} ${instance-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}' Tags: - Key: Name Value: FlowLogForPublicSubnet1 - Key: Purpose Value: AcceptTraffic PublicRouteTable: Type: AWS::EC2::RouteTable Condition: isNotAppStream Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${Namespace} public routes DefaultPublicRoute: Type: AWS::EC2::Route Condition: isNotAppStream DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: isNotAppStream Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 EncryptionKey: Type: AWS::KMS::Key Properties: Description: 'This is the key used to secure resources in this account' EnableKeyRotation: True KeyPolicy: Version: '2012-10-17' Statement: - Sid: Allow root access Effect: 'Allow' Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: - 'kms:*' Resource: '*' - Sid: Allow use of the key by this account Effect: 'Allow' Principal: AWS: '*' Action: - 'kms:DescribeKey' - 'kms:Encrypt' - 'kms:Decrypt' - 'kms:ReEncrypt*' - 'kms:GenerateDataKey' - 'kms:GenerateDataKeyWithoutPlaintext' - 'kms:CreateGrant' - 'kms:RevokeGrant' Resource: '*' Condition: StringEquals: kms:CallerAccount: !Ref 'AWS::AccountId' EncryptionKeyAlias: Type: AWS::KMS::Alias Properties: AliasName: !Join ['', ['alias/', Ref: Namespace, '-encryption-key']] TargetKeyId: !Ref EncryptionKey FlowLogCloudwatchGroup: Type: 'AWS::Logs::LogGroup' DeletionPolicy: Retain Properties: LogGroupName: !Join ['', [Ref: Namespace, '-flow-logger']] RetentionInDays: 30 #------------AppStream Resources Below------- PrivateAppStreamSubnet: Type: AWS::EC2::Subnet Condition: isAppStream Properties: VpcId: !Ref VPC AvailabilityZone: !Select [0, !GetAZs ''] CidrBlock: !Ref AppStreamSubnetCidr MapPublicIpOnLaunch: false Tags: - Key: Name Value: Private AppStream Subnet FlowLogPrivateAppStreamSubnet: Type: AWS::EC2::FlowLog Condition: enableFlowLogsWithAppStream Properties: ResourceId: !Ref PrivateAppStreamSubnet ResourceType: Subnet TrafficType: ACCEPT DeliverLogsPermissionArn: !GetAtt CrossAccountRoleEnvMgmt.Arn LogGroupName: !Ref FlowLogCloudwatchGroup LogFormat: '${version} ${vpc-id} ${subnet-id} ${instance-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}' Tags: - Key: Name Value: FlowLogPrivateAppStreamSubnet - Key: Purpose Value: AcceptTraffic PrivateWorkspaceSubnet: Type: AWS::EC2::Subnet Condition: isAppStream Properties: VpcId: !Ref VPC AvailabilityZone: !Select [0, !GetAZs ''] CidrBlock: !Ref WorkspaceSubnetCidr MapPublicIpOnLaunch: false Tags: - Key: Name Value: Private Workspace Subnet FlowLogPrivateWorkspaceSubnet: Type: AWS::EC2::FlowLog Condition: enableFlowLogsWithAppStream Properties: ResourceId: !Ref PrivateWorkspaceSubnet ResourceType: Subnet TrafficType: ACCEPT DeliverLogsPermissionArn: !GetAtt CrossAccountRoleEnvMgmt.Arn LogGroupName: PrivateWorkspaceSubnetLogGroup LogFormat: '${version} ${vpc-id} ${subnet-id} ${instance-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}' Tags: - Key: Name Value: FlowLogForPrivateWorkspaceSubnet - Key: Purpose Value: AcceptTraffic PrivateWorkspaceRouteTable: Type: 'AWS::EC2::RouteTable' Condition: isAppStream Properties: VpcId: !Ref VPC WorkspaceSubnetAssociationRouteTable: Type: 'AWS::EC2::SubnetRouteTableAssociation' Condition: isAppStream Properties: SubnetId: !Ref PrivateWorkspaceSubnet RouteTableId: !Ref PrivateWorkspaceRouteTable # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpoint.html S3Endpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Sid: 'S3 Read/Write Access' Effect: Allow Principal: '*' Action: - 's3:GetObject' - 's3:GetObjectTagging' - 's3:GetObjectVersion' - 's3:GetObjectVersionTagging' - 's3:PutObject' - 's3:PutObjectAcl' - 's3:PutObjectTagging' - 's3:PutObjectVersionTagging' - 's3:DeleteObject' - 's3:DeleteObjectTagging' - 's3:DeleteObjectVersion' - 's3:DeleteObjectVersionTagging' - 's3:ListBucket' # Required in get_bootstrap.sh when running `aws s3 sync` - 's3:ListBucketVersions' Resource: - !Sub 'arn:aws:s3:::${EgressStoreBucketName}' - !Sub 'arn:aws:s3:::${EgressStoreBucketName}/*' - !Sub 'arn:aws:s3:::${StudyDataBucketName}' - !Sub 'arn:aws:s3:::${StudyDataBucketName}/*' - Sid: 'S3 Read Only Access' Effect: Allow Principal: '*' Action: - 's3:GetObject' - 's3:GetObjectTagging' - 's3:GetObjectVersion' - 's3:GetObjectVersionTagging' - 's3:ListBucket' # Required in get_bootstrap.sh when running `aws s3 sync` - 's3:ListBucketVersions' Resource: '*' RouteTableIds: - !Ref PrivateWorkspaceRouteTable VpcEndpointType: Gateway ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' VpcId: !Ref VPC S3NonAppStreamEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isNotAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 's3:GetObject' - 's3:GetObjectTagging' - 's3:GetObjectTorrent' - 's3:GetObjectVersion' - 's3:GetObjectVersionTagging' - 's3:GetObjectVersionTorrent' - 's3:AbortMultipartUpload' - 's3:ListMultipartUploadParts' - 's3:PutObject' - 's3:PutObjectAcl' - 's3:PutObjectTagging' - 's3:PutObjectVersionTagging' - 's3:DeleteObject' - 's3:DeleteObjectTagging' - 's3:DeleteObjectVersion' - 's3:DeleteObjectVersionTagging' - 's3:ListBucket' # Required in get_bootstrap.sh when running `aws s3 sync` - 's3:ListBucketVersions' Resource: - '*' RouteTableIds: - !Ref PublicRouteTable VpcEndpointType: Gateway ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' VpcId: !Ref VPC # Do not edit this policy or it may remove access to onboarded BYOB studies--see VPCE Policy Service KMSEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 'kms:Decrypt' - 'kms:DescribeKey' - 'kms:Encrypt' - 'kms:GenerateDataKey' - 'kms:ReEncrypt*' Resource: - !Sub 'arn:aws:kms:${AWS::Region}:${CentralAccountId}:key/*' Condition: StringEquals: aws:ResourceAccount: - Ref: CentralAccountId SubnetIds: - !Ref PrivateWorkspaceSubnet PrivateDnsEnabled: true VpcEndpointType: Interface ServiceName: !Sub 'com.amazonaws.${AWS::Region}.kms' VpcId: !Ref VPC SecurityGroupIds: - !Ref InterfaceEndpointSecurityGroup # Do not edit this policy or it may remove access to onboarded BYOB studies--see VPCE Policy Service STSEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Sid: AllowAssumeRole Effect: Allow Principal: '*' Action: - 'sts:AssumeRole' Resource: - !Sub 'arn:aws:iam::${CentralAccountId}:role/swb-study-*' SubnetIds: - !Ref PrivateWorkspaceSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.sts' VpcId: !Ref VPC SecurityGroupIds: - !Ref InterfaceEndpointSecurityGroup EC2Endpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Sid: AllowPrefixListDescription Effect: Allow Principal: '*' Action: - 'ec2:DescribeManagedPrefixLists' - 'ec2:DescribePrefixLists' Resource: '*' SubnetIds: - !Ref PrivateWorkspaceSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2' VpcId: !Ref VPC SecurityGroupIds: - !Ref InterfaceEndpointSecurityGroup CfnEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Sid: AllowSignalResource Effect: Allow Principal: '*' Action: - 'cloudformation:SignalResource' Resource: - !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/SC-${AWS::AccountId}-pp-*' SubnetIds: - !Ref PrivateWorkspaceSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.cloudformation' VpcId: !Ref VPC SecurityGroupIds: - !Ref InterfaceEndpointSecurityGroup WorkspaceSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: isAppStream Properties: GroupDescription: 'Security Group for AppStream instances to connect with environments, and for environments to connect with interface endpoints' GroupName: 'Workspace-SG' VpcId: !Ref VPC WorkspaceSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Condition: isAppStream Properties: GroupId: !Ref WorkspaceSecurityGroup SourceSecurityGroupId: !Ref AppStreamSecurityGroup Description: 'Allow AppStream ingress from environments' IpProtocol: '-1' WorkspaceSecurityGroupEgress: Type: AWS::EC2::SecurityGroupEgress Condition: isAppStream Properties: GroupId: !Ref WorkspaceSecurityGroup DestinationSecurityGroupId: !Ref InterfaceEndpointSecurityGroup Description: 'Allow Interface Endpoint egress from environments' IpProtocol: '-1' InterfaceEndpointSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: isAppStream Properties: GroupDescription: 'Security Group for interface endpoints' GroupName: 'Interface-Endpoint-SG' VpcId: !Ref VPC InterfaceEndpointSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Condition: isAppStream Properties: GroupId: !Ref InterfaceEndpointSecurityGroup SourceSecurityGroupId: !Ref WorkspaceSecurityGroup Description: 'Allow environment ingress from interface endpoints' IpProtocol: '-1' SSMEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStreamAndCustomDomain Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 'ssm:PutParameter' Resource: - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/rstudio/publickey/sc-environments/ec2-instance/*' Condition: StringEquals: aws:ResourceAccount: - Ref: AWS::AccountId SubnetIds: - !Ref PrivateWorkspaceSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm' VpcId: !Ref VPC SecurityGroupIds: - !Ref InterfaceEndpointSecurityGroup # https://docs.aws.amazon.com/sagemaker/latest/dg/notebook-interface-endpoint.html SagemakerNotebookEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 'sagemaker:DescribeNotebookInstance' - 'sagemaker:StopNotebookInstance' - 'sagemaker:CreatePresignedNotebookInstanceUrl' Resource: - !Sub 'arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:notebook-instance/*' Condition: StringEquals: aws:ResourceAccount: - Ref: AWS::AccountId SubnetIds: - !Ref PrivateAppStreamSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'aws.sagemaker.${AWS::Region}.notebook' SecurityGroupIds: - !Ref SageMakerSecurityGroup VpcId: !Ref VPC SagemakerApiEndpoint: Type: 'AWS::EC2::VPCEndpoint' Condition: isAppStream Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: '*' Action: - 'sagemaker:DescribeNotebookInstance' - 'sagemaker:StopNotebookInstance' - 'sagemaker:CreatePresignedNotebookInstanceUrl' Resource: - !Sub 'arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:notebook-instance/*' Condition: StringEquals: aws:ResourceAccount: - Ref: AWS::AccountId SubnetIds: - !Ref PrivateWorkspaceSubnet VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.sagemaker.api' VpcId: !Ref VPC SecurityGroupIds: - !Ref SagemakerApiEndpointSecurityGroup SagemakerApiEndpointSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: isAppStream Properties: GroupDescription: 'Sagemaker Api Endpoint Security Group for interface endpoint' GroupName: 'Sagemaker-API-SG' VpcId: !Ref VPC SecurityGroupIngress: - SourceSecurityGroupId: !Ref WorkspaceSecurityGroup IpProtocol: '-1' SageMakerSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: isAppStream Properties: GroupDescription: 'SWB SageMaker Security Group for interface endpoint' GroupName: 'SageMakerSG' VpcId: !Ref VPC SecurityGroupIngress: - SourceSecurityGroupId: !Ref AppStreamSecurityGroup IpProtocol: '-1' AppStreamSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: isAppStream Properties: GroupDescription: 'SWB AppStream Security Group' GroupName: 'AppStreamSG' VpcId: !Ref VPC SecurityGroupEgress: - CidrIp: 127.0.0.1/32 IpProtocol: '-1' - DestinationSecurityGroupId: !Ref WorkspaceSecurityGroup IpProtocol: '-1' AppStreamSecurityGroupEgress: Type: AWS::EC2::SecurityGroupEgress Condition: isAppStream Properties: GroupId: !Ref AppStreamSecurityGroup DestinationSecurityGroupId: !Ref SageMakerSecurityGroup Description: 'Allow SageMaker egress from AppStream instances' IpProtocol: '-1' SageMakerSecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Condition: isAppStream Properties: GroupId: !Ref SageMakerSecurityGroup SourceSecurityGroupId: !Ref AppStreamSecurityGroup Description: 'Allow AppStream ingress from interface endpoint of SageMaker' IpProtocol: '-1' AppStreamFleet: Type: 'AWS::AppStream::Fleet' Condition: isAppStream Properties: ComputeCapacity: DesiredInstances: !Ref AppStreamFleetDesiredInstances Description: 'SWB AppStream Fleet' DisconnectTimeoutInSeconds: !Ref AppStreamDisconnectTimeoutSeconds DisplayName: 'SWB Fleet' EnableDefaultInternetAccess: False FleetType: !Ref AppStreamFleetType IdleDisconnectTimeoutInSeconds: !Ref AppStreamIdleDisconnectTimeoutSeconds ImageArn: !If [ amiSharingEnabled, !Sub 'arn:aws:appstream:${AWS::Region}:${DevopsAccountId}:image/${AppStreamImageName}', !Sub 'arn:aws:appstream:${AWS::Region}:${CentralAccountId}:image/${AppStreamImageName}', ] InstanceType: !Ref AppStreamInstanceType MaxUserDurationInSeconds: !Ref AppStreamMaxUserDurationSeconds Name: !Sub ${Namespace}-ServiceWorkbenchFleet StreamView: 'APP' VpcConfig: SecurityGroupIds: - !Ref AppStreamSecurityGroup SubnetIds: - !Ref PrivateAppStreamSubnet AppStreamStack: Type: 'AWS::AppStream::Stack' Condition: isAppStream Properties: ApplicationSettings: Enabled: False Description: 'SWB AppStream Stack' DisplayName: 'SWB Stack' Name: !Sub ${Namespace}-ServiceWorkbenchStack UserSettings: - Action: 'CLIPBOARD_COPY_FROM_LOCAL_DEVICE' Permission: 'ENABLED' - Action: 'CLIPBOARD_COPY_TO_LOCAL_DEVICE' Permission: 'DISABLED' - Action: 'FILE_DOWNLOAD' Permission: 'DISABLED' - Action: 'FILE_UPLOAD' Permission: 'DISABLED' - Action: 'PRINTING_TO_LOCAL_DEVICE' Permission: 'DISABLED' AppStreamStackFleetAssociation: Type: AWS::AppStream::StackFleetAssociation Condition: isAppStream Properties: FleetName: !Ref AppStreamFleet StackName: !Ref AppStreamStack Outputs: CrossAccountEnvMgmtRoleArn: Description: The arn of the cross account role for environment management using AWS Service Catalog Value: !GetAtt [CrossAccountRoleEnvMgmt, Arn] CrossAccountExecutionRoleArn: Description: The arn of the cross account role. Value: !GetAtt [CrossAccountExecutionRole, Arn] VPC: Description: VPC ID Value: !Ref VPC VpcPublicSubnet1: Description: A reference to the public subnet in the 1st Availability Zone Condition: isNotAppStream Value: !Ref PublicSubnet1 EncryptionKeyArn: Description: KMS Encryption Key Arn Value: !GetAtt [EncryptionKey, Arn] OnboardStatusRoleArn: Description: The arn of the role SWB uses to check permissions status Value: !GetAtt [CfnStatusRole, Arn] PublicRouteTableId: Condition: isNotAppStream Description: The public route table assigned to the workspace VPC Value: !Ref PublicRouteTable S3NonAppStreamVPCE: Description: S3 interface endpoint Condition: isNotAppStream Value: !Ref S3NonAppStreamEndpoint Export: Name: !Join ['', [Ref: Namespace, '-S3NonAppStreamVPCE']] #------------AppStream Output Below------- PrivateAppStreamSubnet: Description: AppStream subnet Condition: isAppStream Value: !Ref PrivateAppStreamSubnet PrivateWorkspaceSubnet: Description: Workspace subnet Condition: isAppStream Value: !Ref PrivateWorkspaceSubnet AppStreamSecurityGroup: Description: AppStream Security Group Condition: isAppStream Value: !Ref AppStreamSecurityGroup Export: Name: !Join ['', [Ref: Namespace, '-SwbAppStreamSG']] SageMakerSecurityGroup: Description: SageMaker Security Group Condition: isAppStream Value: !Ref SageMakerSecurityGroup AppStreamFleet: Description: AppStream Fleet Condition: isAppStream Value: !Ref AppStreamFleet AppStreamStack: Description: AppStream Stack Condition: isAppStream Value: !Ref AppStreamStack InterfaceEndpointSG: Description: Security group of Interface endpoints Condition: isAppStream Value: !Ref InterfaceEndpointSecurityGroup Export: Name: !Join ['', [Ref: Namespace, '-InterfaceEndpointSG']] WorkspaceSG: Description: Security Group for AppStream instances to connect with environments, and for environments to connect with interface endpoints Condition: isAppStream Value: !Ref WorkspaceSecurityGroup Export: Name: !Join ['', [Ref: Namespace, '-WorkspaceSG']] AppStreamStackName: Description: Name of the stack created by AppStream Condition: isAppStream Value: !Sub ${Namespace}-ServiceWorkbenchStack SageMakerVPCE: Description: SageMaker interface endpoint Condition: isAppStream Value: !Ref SagemakerNotebookEndpoint Export: Name: !Join ['', [Ref: Namespace, '-SageMakerVPCE']] SageMakerApiSG: Description: SageMaker API SG Condition: isAppStream Value: !Ref SagemakerApiEndpointSecurityGroup Export: Name: !Join ['', [Ref: Namespace, '-SageMakerApiSecurityGroup']] Route53HostedZone: Description: Route53 hosted zone Condition: isAppStreamAndCustomDomain Value: !Ref Route53HostedZone S3AppStreamVPCE: Description: S3 interface endpoint Condition: isAppStream Value: !Ref S3Endpoint Export: Name: !Join ['', [Ref: Namespace, '-S3AppStreamVPCE']] KMSVPCE: Description: KMS interface endpoint Condition: isAppStream Value: !Ref KMSEndpoint Export: Name: !Join ['', [Ref: Namespace, '-KMSVPCE']] STSVPCE: Description: STS interface endpoint Condition: isAppStream Value: !Ref STSEndpoint Export: Name: !Join ['', [Ref: Namespace, '-STSVPCE']]