From 014fc7b50c7040c1240d41367c9783c675742047 Mon Sep 17 00:00:00 2001 From: danny randall <10566468+dannyrandall@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:43:41 -0700 Subject: [PATCH 1/7] chore: arps manifest updates, cfn template (#4111) * chore: arps manifest updates, cfn template * fix tests * address some feedback, staticcheck * add truncate function * fix integ tests --- .../pkg/deploy/cloudformation/stack/env.go | 5 ++ .../stack/env_integration_test.go | 1 - .../deploy/cloudformation/stack/env_test.go | 52 +++++++++++++++++++ .../deploy/cloudformation/stack/rd_web_svc.go | 4 +- .../template-with-basic-manifest.yml | 48 ++++++++++++++++- .../template-with-custom-security-group.yml | 51 ++++++++++++++++-- ...emplate-with-default-access-log-config.yml | 49 ++++++++++++++++- .../template-with-defaultvpc-flowlogs.yml | 49 ++++++++++++++++- ...late-with-imported-certs-observability.yml | 50 +++++++++++++++++- ...-sslpolicy-custom-empty-security-group.yml | 51 ++++++++++++++++-- .../template-with-importedvpc-flowlogs.yml | 48 ++++++++++++++++- .../testdata/workloads/rdws-prod.stack.yml | 7 ++- .../testdata/workloads/rdws-test.stack.yml | 7 ++- internal/pkg/deploy/env.go | 2 +- internal/pkg/manifest/rd_web_svc.go | 15 +++++- internal/pkg/manifest/validate.go | 9 +++- internal/pkg/template/env.go | 44 ++++++++++------ internal/pkg/template/env_test.go | 37 +++++++++++++ .../pkg/template/templates/environment/cf.yml | 13 ++++- .../environment/partials/ar-vpc-connector.yml | 41 +++++++++++++++ .../partials/cfn-execution-role.yml | 8 ++- .../workloads/partials/cf/accessrole.yml | 2 + .../workloads/partials/cf/instancerole.yml | 5 +- .../workloads/partials/cf/vpc-connector.yml | 2 +- .../workloads/services/rd-web/cf.yml | 47 +++++++++++++++-- internal/pkg/template/workload.go | 13 +++-- 26 files changed, 610 insertions(+), 50 deletions(-) create mode 100644 internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml diff --git a/internal/pkg/deploy/cloudformation/stack/env.go b/internal/pkg/deploy/cloudformation/stack/env.go index 16afecd3a7e..e29e7cb068c 100644 --- a/internal/pkg/deploy/cloudformation/stack/env.go +++ b/internal/pkg/deploy/cloudformation/stack/env.go @@ -42,6 +42,7 @@ const ( envParamInternalALBWorkloadsKey = "InternalALBWorkloads" envParamEFSWorkloadsKey = "EFSWorkloads" envParamNATWorkloadsKey = "NATWorkloads" + envParamAppRunnerPrivateWorkloadsKey = "AppRunnerPrivateWorkloads" envParamCreateHTTPSListenerKey = "CreateHTTPSListener" envParamCreateInternalHTTPSListenerKey = "CreateInternalHTTPSListener" EnvParamServiceDiscoveryEndpoint = "ServiceDiscoveryEndpoint" @@ -194,6 +195,10 @@ func (e *EnvStackConfig) Parameters() ([]*cloudformation.Parameter, error) { ParameterKey: aws.String(envParamNATWorkloadsKey), ParameterValue: aws.String(""), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, } if e.prevParams == nil { return currParams, nil diff --git a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go index 1cd6ee5010b..179bb23cac1 100644 --- a/internal/pkg/deploy/cloudformation/stack/env_integration_test.go +++ b/internal/pkg/deploy/cloudformation/stack/env_integration_test.go @@ -250,7 +250,6 @@ network: }(), wantedFileName: "template-with-defaultvpc-flowlogs.yml", }, - "generate template with imported vpc and flowlogs is on": { input: func() *deploy.CreateEnvironmentInput { rawMft := `name: test diff --git a/internal/pkg/deploy/cloudformation/stack/env_test.go b/internal/pkg/deploy/cloudformation/stack/env_test.go index 97c3680d701..526ccf54e03 100644 --- a/internal/pkg/deploy/cloudformation/stack/env_test.go +++ b/internal/pkg/deploy/cloudformation/stack/env_test.go @@ -163,6 +163,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, "with DNS": { @@ -220,6 +224,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, "with private DNS only": { @@ -277,6 +285,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("true"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, "should use default value for new EnvControllerParameters": { @@ -330,6 +342,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String("rdws-backend"), + }, }, want: []*cloudformation.Parameter{ @@ -385,6 +401,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String("rdws-backend"), + }, }, }, "should retain the values from EnvControllerParameters": { @@ -398,6 +418,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamNATWorkloadsKey), ParameterValue: aws.String("backend"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String("rdws-backend"), + }, { ParameterKey: aws.String(EnvParamAliasesKey), ParameterValue: aws.String(""), @@ -497,6 +521,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String("rdws-backend"), + }, }, }, "should not include old parameters that are deleted": { @@ -557,6 +585,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, want: []*cloudformation.Parameter{ @@ -612,6 +644,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, "should reuse old service discovery endpoint value": { @@ -669,6 +705,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, want: []*cloudformation.Parameter{ @@ -724,6 +764,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, "should use app.local endpoint service discovery endpoint if it is a new parameter": { @@ -753,6 +797,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamAppDNSDelegationRoleKey), ParameterValue: aws.String(""), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, want: []*cloudformation.Parameter{ @@ -808,6 +856,10 @@ func TestEnv_Parameters(t *testing.T) { ParameterKey: aws.String(envParamCreateInternalHTTPSListenerKey), ParameterValue: aws.String("false"), }, + { + ParameterKey: aws.String(envParamAppRunnerPrivateWorkloadsKey), + ParameterValue: aws.String(""), + }, }, }, } diff --git a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go index 6099355e2e9..c7c144de853 100644 --- a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go @@ -140,7 +140,9 @@ func (s *RequestDrivenWebService) Template() (string, error) { Observability: template.ObservabilityOpts{ Tracing: strings.ToUpper(aws.StringValue(s.manifest.Observability.Tracing)), }, - PermissionsBoundary: s.permBound, + PermissionsBoundary: s.permBound, + Private: !s.manifest.Private.IsZero(), + AppRunnerVPCEndpoint: s.manifest.Private.Advanced.Endpoint, }) if err != nil { return "", err diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml index c906eb6fd6a..40fbdba78e1 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml @@ -18,6 +18,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -51,6 +53,8 @@ Conditions: !Not [!Equals [ !Ref EFSWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -993,6 +997,42 @@ Resources: DomainName: !Ref AppDNSName PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1124,7 +1164,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-SubDomain EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1138,3 +1178,9 @@ Outputs: LastForceDeployID: Value: "" Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml index 3ad2b44dd8b..421ea110252 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml @@ -40,6 +40,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -73,6 +75,8 @@ Conditions: !Not [!Equals [ !Ref InternalALBWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -905,7 +909,42 @@ Resources: - 'cloudformation:DeleteStack' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1025,7 +1064,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-CFNExecutionRoleARN EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1038,4 +1077,10 @@ Outputs: Value: true LastForceDeployID: Value: "" - Description: Optionally force the template to update when no immediate resource change is present. \ No newline at end of file + Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml index 5475b10cedb..4f42f56054d 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml @@ -21,6 +21,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -54,6 +56,8 @@ Conditions: !Not [!Equals [ !Ref EFSWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -1089,6 +1093,42 @@ Resources: DomainName: !Ref AppDNSName PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1220,7 +1260,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-SubDomain EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1234,3 +1274,10 @@ Outputs: LastForceDeployID: Value: "" Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId + diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index f61bc678f74..2c3d9bbbffb 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -22,6 +22,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -55,6 +57,8 @@ Conditions: !Not [!Equals [ !Ref EFSWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -1041,6 +1045,42 @@ Resources: - logs:DescribeLogGroups - logs:DescribeLogStreams Resource: "*" + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1172,7 +1212,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-SubDomain EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1186,3 +1226,10 @@ Outputs: LastForceDeployID: Value: "" Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId + diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml index 92912532170..e4df2b99358 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml @@ -37,6 +37,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -70,6 +72,8 @@ Conditions: !Not [!Equals [ !Ref InternalALBWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -985,7 +989,42 @@ Resources: - 'cloudformation:DeleteStack' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1105,7 +1144,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-CFNExecutionRoleARN EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1125,3 +1164,10 @@ Outputs: LastForceDeployID: Value: "" Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId + diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml index 8ae2be51091..c2e9e17ed9e 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml @@ -30,6 +30,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -63,6 +65,8 @@ Conditions: !Not [!Equals [ !Ref InternalALBWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -882,7 +886,42 @@ Resources: - 'cloudformation:DeleteStack' Resource: - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*' - + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: !Ref VPC + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: !Ref VPC @@ -1002,7 +1041,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-CFNExecutionRoleARN EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1015,4 +1054,10 @@ Outputs: Value: true LastForceDeployID: Value: "" - Description: Optionally force the template to update when no immediate resource change is present. \ No newline at end of file + Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index 2f239575d22..0c94b7ad79b 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -30,6 +30,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -63,6 +65,8 @@ Conditions: !Not [!Equals [ !Ref EFSWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -871,6 +875,42 @@ Resources: - logs:DescribeLogGroups - logs:DescribeLogStreams Resource: "*" + AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: demo-test-AppRunnerVpcEndpointSecurityGroup + VpcId: vpc-12345 + Tags: + - Key: Name + Value: copilot-demo-test-app-runner-vpc-endpoint + + AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + + AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 Outputs: VpcId: Value: vpc-12345 @@ -989,7 +1029,7 @@ Outputs: Export: Name: !Sub ${AWS::StackName}-SubDomain EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -1003,3 +1043,9 @@ Outputs: LastForceDeployID: Value: "" Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml index 336c4c7b2bb..c09e368d571 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml @@ -71,6 +71,8 @@ Resources: Principal: Service: - build.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: @@ -86,7 +88,10 @@ Resources: Statement: - Effect: Allow Principal: - Service: tasks.apprunner.amazonaws.com + Service: + - tasks.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles' diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml index fe2d9c0720e..d986cd339b3 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml @@ -71,6 +71,8 @@ Resources: Principal: Service: - build.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: @@ -86,7 +88,10 @@ Resources: Statement: - Effect: Allow Principal: - Service: tasks.apprunner.amazonaws.com + Service: + - tasks.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles' diff --git a/internal/pkg/deploy/env.go b/internal/pkg/deploy/env.go index fc65ac59ce7..1d1c5a28185 100644 --- a/internal/pkg/deploy/env.go +++ b/internal/pkg/deploy/env.go @@ -14,7 +14,7 @@ const ( // LegacyEnvTemplateVersion is the version associated with the environment template before we started versioning. LegacyEnvTemplateVersion = "v0.0.0" // LatestEnvTemplateVersion is the latest version number available for environment templates. - LatestEnvTemplateVersion = "v1.12.3" + LatestEnvTemplateVersion = "v1.13.0" EnvTemplateVersionBootstrap = "bootstrap" ) diff --git a/internal/pkg/manifest/rd_web_svc.go b/internal/pkg/manifest/rd_web_svc.go index dfae5817d28..45a604bdb51 100644 --- a/internal/pkg/manifest/rd_web_svc.go +++ b/internal/pkg/manifest/rd_web_svc.go @@ -81,8 +81,19 @@ func (c *rdwsVpcConfig) isEmpty() bool { // RequestDrivenWebServiceHttpConfig represents options for configuring http. type RequestDrivenWebServiceHttpConfig struct { - HealthCheckConfiguration HealthCheckArgsOrString `yaml:"healthcheck"` - Alias *string `yaml:"alias"` + HealthCheckConfiguration HealthCheckArgsOrString `yaml:"healthcheck"` + Alias *string `yaml:"alias"` + Private Union[bool, VPCEndpoint] `yaml:"private"` +} + +// VPCEndpoint is used to configure a pre-existing VPC endpoint. +type VPCEndpoint struct { + Endpoint *string `yaml:"endpoint"` +} + +// IsZero implements yaml.IsZeroer. +func (v VPCEndpoint) IsZero() bool { + return v.Endpoint == nil } // AppRunnerInstanceConfig contains the instance configuration properties for an App Runner service. diff --git a/internal/pkg/manifest/validate.go b/internal/pkg/manifest/validate.go index 83398480013..993ead1a9c3 100644 --- a/internal/pkg/manifest/validate.go +++ b/internal/pkg/manifest/validate.go @@ -1388,7 +1388,14 @@ func (r AppRunnerInstanceConfig) validate() error { // validate returns nil if RequestDrivenWebServiceHttpConfig is configured correctly. func (r RequestDrivenWebServiceHttpConfig) validate() error { - return r.HealthCheckConfiguration.validate() + if err := r.HealthCheckConfiguration.validate(); err != nil { + return err + } + return r.Private.validate() +} + +func (v VPCEndpoint) validate() error { + return nil } // validate returns nil if Observability is configured correctly. diff --git a/internal/pkg/template/env.go b/internal/pkg/template/env.go index b7c8a34b2b2..ca8f73df88c 100644 --- a/internal/pkg/template/env.go +++ b/internal/pkg/template/env.go @@ -18,35 +18,38 @@ const ( // Available env-controller managed feature names. const ( - ALBFeatureName = "ALBWorkloads" - EFSFeatureName = "EFSWorkloads" - NATFeatureName = "NATWorkloads" - InternalALBFeatureName = "InternalALBWorkloads" - AliasesFeatureName = "Aliases" + ALBFeatureName = "ALBWorkloads" + EFSFeatureName = "EFSWorkloads" + NATFeatureName = "NATWorkloads" + InternalALBFeatureName = "InternalALBWorkloads" + AliasesFeatureName = "Aliases" + AppRunnerPrivateServiceFeatureName = "AppRunnerPrivateWorkloads" ) // LastForceDeployIDOutputName is the logical ID of the deployment controller output. const LastForceDeployIDOutputName = "LastForceDeployID" var friendlyEnvFeatureName = map[string]string{ - ALBFeatureName: "ALB", - EFSFeatureName: "EFS", - NATFeatureName: "NAT Gateway", - InternalALBFeatureName: "Internal ALB", - AliasesFeatureName: "Aliases", + ALBFeatureName: "ALB", + EFSFeatureName: "EFS", + NATFeatureName: "NAT Gateway", + InternalALBFeatureName: "Internal ALB", + AliasesFeatureName: "Aliases", + AppRunnerPrivateServiceFeatureName: "App Runner Private Services", } var leastVersionForFeature = map[string]string{ - ALBFeatureName: "v1.0.0", - EFSFeatureName: "v1.3.0", - NATFeatureName: "v1.3.0", - InternalALBFeatureName: "v1.10.0", - AliasesFeatureName: "v1.4.0", + ALBFeatureName: "v1.0.0", + EFSFeatureName: "v1.3.0", + NATFeatureName: "v1.3.0", + InternalALBFeatureName: "v1.10.0", + AliasesFeatureName: "v1.4.0", + AppRunnerPrivateServiceFeatureName: "v1.23.0", } // AvailableEnvFeatures returns a list of the latest available feature, named after their corresponding parameter names. func AvailableEnvFeatures() []string { - return []string{ALBFeatureName, EFSFeatureName, NATFeatureName, InternalALBFeatureName, AliasesFeatureName} + return []string{ALBFeatureName, EFSFeatureName, NATFeatureName, InternalALBFeatureName, AliasesFeatureName, AppRunnerPrivateServiceFeatureName} } // FriendlyEnvFeatureName returns a user-friendly feature name given a env-controller managed parameter name. @@ -78,6 +81,7 @@ var ( "bootstrap-resources", "elb-access-logs", "mappings-regional-configs", + "ar-vpc-connector", } ) @@ -252,6 +256,14 @@ func withEnvParsingFuncs() ParseOption { "inc": IncFunc, "fmtSlice": FmtSliceFunc, "quote": strconv.Quote, + "truncate": truncate, }) } } + +func truncate(s string, maxLen int) string { + if len(s) < maxLen { + return s + } + return s[:maxLen] +} diff --git a/internal/pkg/template/env_test.go b/internal/pkg/template/env_test.go index 840d4b3f703..d09e2286637 100644 --- a/internal/pkg/template/env_test.go +++ b/internal/pkg/template/env_test.go @@ -26,6 +26,7 @@ func TestTemplate_ParseEnv(t *testing.T) { "templates/environment/partials/bootstrap-resources.yml": []byte("bootstrap"), "templates/environment/partials/elb-access-logs.yml": []byte("elb-access-logs"), "templates/environment/partials/mappings-regional-configs.yml": []byte("mappings-regional-configs"), + "templates/environment/partials/ar-vpc-connector.yml": []byte("ar-vpc-connector"), }, }, } @@ -58,3 +59,39 @@ func TestTemplate_ParseEnvBootstrap(t *testing.T) { require.NoError(t, err) require.Equal(t, "test", c.String()) } + +func TestTruncate(t *testing.T) { + tests := map[string]struct { + s string + maxLen int + + expected string + }{ + "empty string": { + s: "", + maxLen: 10, + expected: "", + }, + "maxLen < len(string)": { + s: "qwerty", + maxLen: 4, + expected: "qwer", + }, + "maxLen > len(string)": { + s: "qwerty", + maxLen: 7, + expected: "qwerty", + }, + "maxLen == len(string)": { + s: "qwerty", + maxLen: 6, + expected: "qwerty", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expected, truncate(tc.s, tc.maxLen)) + }) + } +} diff --git a/internal/pkg/template/templates/environment/cf.yml b/internal/pkg/template/templates/environment/cf.yml index b6c84a1830a..f80cfd6cdb7 100644 --- a/internal/pkg/template/templates/environment/cf.yml +++ b/internal/pkg/template/templates/environment/cf.yml @@ -20,6 +20,8 @@ Parameters: Type: String NATWorkloads: Type: String + AppRunnerPrivateWorkloads: + Type: String ToolsAccountPrincipalARN: Type: String AppDNSName: @@ -53,6 +55,8 @@ Conditions: !Not [!Equals [ !Ref EFSWorkloads, ""]] CreateNATGateways: !Not [!Equals [ !Ref NATWorkloads, ""]] + CreateAppRunnerVPCEndpoint: + !Not [!Equals [ !Ref AppRunnerPrivateWorkloads, ""]] ManagedAliases: !And - !Condition DelegateDNS - !Not [!Equals [ !Ref Aliases, "" ]] @@ -563,6 +567,7 @@ Resources: {{include "lambdas" . | indent 2}} {{include "custom-resources" . | indent 2}} {{- end}} +{{include "ar-vpc-connector" . | indent 2}} {{- if .VPCConfig.FlowLogs }} FlowLog: Metadata: @@ -774,7 +779,7 @@ Outputs: Name: !Sub ${AWS::StackName}-SubDomain {{- end}} EnabledFeatures: - Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases}' + Value: !Sub '${ALBWorkloads},${InternalALBWorkloads},${EFSWorkloads},${NATWorkloads},${Aliases},${AppRunnerPrivateWorkloads}' Description: Required output to force the stack to update if mutating feature params, like ALBWorkloads, does not change the template. ManagedFileSystemID: Condition: CreateEFS @@ -800,3 +805,9 @@ Outputs: LastForceDeployID: Value: {{quote .ForceUpdateID}} Description: Optionally force the template to update when no immediate resource change is present. + AppRunnerVpcEndpointId: + Condition: CreateAppRunnerVPCEndpoint + Value: !Ref AppRunnerVpcEndpoint + Description: VPC Endpoint to App Runner for private services + Export: + Name: !Sub ${AWS::StackName}-AppRunnerVpcEndpointId diff --git a/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml b/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml new file mode 100644 index 00000000000..19c3b0864f7 --- /dev/null +++ b/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml @@ -0,0 +1,41 @@ +AppRunnerVpcEndpointSecurityGroup: + Metadata: + 'aws:copilot:description': 'A security group for App Runner private services' + Type: AWS::EC2::SecurityGroup + Condition: CreateAppRunnerVPCEndpoint + Properties: + GroupDescription: {{truncate (printf "%s-%s-AppRunnerVpcEndpointSecurityGroup" .AppName .EnvName) 255}} + {{- if .VPCConfig.Imported}} + VpcId: {{.VPCConfig.Imported.ID}} + {{- else}} + VpcId: !Ref VPC + {{- end}} + Tags: + - Key: Name + Value: {{truncate (printf "copilot-%s-%s-app-runner-vpc-endpoint" .AppName .EnvName) 255}} + +AppRunnerVpcEndpointSecurityGroupIngressFromEnvironment: + Type: AWS::EC2::SecurityGroupIngress + Condition: CreateAppRunnerVPCEndpoint + Properties: + Description: Ingress from services in the environment + GroupId: !Ref AppRunnerVpcEndpointSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref EnvironmentSecurityGroup + +AppRunnerVpcEndpoint: + Metadata: + 'aws:copilot:description': 'VPC Endpoint to connect environment to App Runner for private services' + Type: AWS::EC2::VPCEndpoint + Condition: CreateAppRunnerVPCEndpoint + Properties: + VpcEndpointType: Interface + VpcId: !Ref VPC + SecurityGroupIds: + - !Ref AppRunnerVpcEndpointSecurityGroup + # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' + ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + SubnetIds: + {{- range $ind, $cidr := .VPCConfig.Managed.PrivateSubnetCIDRs}} + - !Ref PrivateSubnet{{inc $ind}} + {{- end}} \ No newline at end of file diff --git a/internal/pkg/template/templates/environment/partials/cfn-execution-role.yml b/internal/pkg/template/templates/environment/partials/cfn-execution-role.yml index 1cc57e8e098..f404d4e137a 100644 --- a/internal/pkg/template/templates/environment/partials/cfn-execution-role.yml +++ b/internal/pkg/template/templates/environment/partials/cfn-execution-role.yml @@ -28,15 +28,13 @@ CloudformationExecutionRole: PolicyDocument: Version: '2012-10-17' Statement: - - - Effect: Allow + - Effect: Allow NotAction: - 'organizations:*' - 'account:*' Resource: '*' - - - Effect: Allow + - Effect: Allow Action: - 'organizations:DescribeOrganization' - 'account:ListRegions' - Resource: '*' + Resource: '*' \ No newline at end of file diff --git a/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml b/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml index 2847045480f..d1421489678 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml @@ -14,6 +14,8 @@ AccessRole: Principal: Service: - build.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: diff --git a/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml b/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml index 6e036199e11..d2d5ab28711 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml @@ -19,7 +19,10 @@ InstanceRole: Statement: - Effect: Allow Principal: - Service: tasks.apprunner.amazonaws.com + Service: + - tasks.apprunner.amazonaws.com + - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release + - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles' diff --git a/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml b/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml index f069750d84b..34cb48cac5a 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml @@ -16,7 +16,7 @@ EnvironmentSecurityGroupIngressFromServiceSecurityGroup: Metadata: 'aws:copilot:description': 'Allow ingress from the app runner service to services in your environment' Properties: - GroupId: + GroupId: Fn::ImportValue: !Sub '${AppName}-${EnvName}-EnvironmentSecurityGroup' IpProtocol: -1 diff --git a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml index e19b6304ed1..2b3a54a28e9 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml @@ -133,12 +133,18 @@ Resources: HealthyThreshold: !If [HasHealthCheckHealthyThreshold, !Ref HealthCheckHealthyThreshold, !Ref AWS::NoValue] UnhealthyThreshold: !If [HasHealthCheckUnhealthyThreshold, !Ref HealthCheckUnhealthyThreshold, !Ref AWS::NoValue] {{- end }} - {{- if requiresVPCConnector .}} NetworkConfiguration: EgressConfiguration: + {{- if requiresVPCConnector .}} EgressType: VPC VpcConnectorArn: !Ref VpcConnector - {{- end }} + {{- else }} + EgressType: DEFAULT + {{- end}} + {{- if .Private}} + IngressConfiguration: + IsPubliclyAccessible: false + {{- end}} {{- if eq .Observability.Tracing "AWSXRAY"}} ObservabilityConfiguration: ObservabilityEnabled: true @@ -154,6 +160,34 @@ Resources: - Key: {{$name}} Value: {{$value}}{{end}}{{end}} + {{- if .Private}} + AppRunnerVpcIngressConnection: + Type: AWS::AppRunner::VpcIngressConnection + Properties: + ServiceArn: !GetAtt Service.ServiceArn + IngressVpcConfiguration: + VpcId: + Fn::ImportValue: !Sub '${AppName}-${EnvName}-VpcId' + {{- if .AppRunnerVPCEndpoint}} + VpcEndpointId: {{.AppRunnerVPCEndpoint}} + {{- else}} + VpcEndpointId: !GetAtt EnvControllerAction.AppRunnerVpcEndpointId + {{- end}} + Tags: + - Key: copilot-application + Value: !Ref AppName + - Key: copilot-environment + Value: !Ref EnvName + - Key: copilot-service + Value: !Ref WorkloadName + {{- if .Tags}} + {{- range $name, $value := .Tags}} + - Key: {{$name}} + Value: {{$value}} + {{- end}} + {{- end}} + {{- end}} + {{include "addons" . | indent 2}} {{if .Alias}} CustomDomainFunction: @@ -229,4 +263,11 @@ Resources: {{- if requiresVPCConnector .}} {{include "vpc-connector" . | indent 2 }} -{{- end }} \ No newline at end of file +{{- end }} + +{{- if .Private}} +# TODO remove after beta SDK works +Outputs: + DomainName: + Value: !GetAtt AppRunnerVpcIngressConnection.DomainName +{{- end}} \ No newline at end of file diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go index 9d33608caaf..47941945517 100644 --- a/internal/pkg/template/workload.go +++ b/internal/pkg/template/workload.go @@ -585,9 +585,11 @@ type WorkloadOpts struct { StateMachine *StateMachineOpts // Additional options for request driven web service templates. - StartCommand *string - EnableHealthCheck bool - Observability ObservabilityOpts + StartCommand *string + EnableHealthCheck bool + Observability ObservabilityOpts + Private bool + AppRunnerVPCEndpoint *string // Input needed for the custom resource that adds a custom domain to the service. Alias *string @@ -732,6 +734,11 @@ func envControllerParameters(o WorkloadOpts) []string { parameters = append(parameters, "InternalALBWorkloads,") } } + if o.WorkloadType == "Request-Driven Web Service" { + if o.Private { + parameters = append(parameters, "AppRunnerPrivateWorkloads,") + } + } if o.Network.SubnetsType == PrivateSubnetsPlacement { parameters = append(parameters, "NATWorkloads,") } From f6e20a01a7445ba06a5e81715a8280ebd16c9a3c Mon Sep 17 00:00:00 2001 From: danny randall <10566468+dannyrandall@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:19:41 -0700 Subject: [PATCH 2/7] chore: add vic metadata, add env sg to vpc connector (#4125) * chore: add vic metadata, add env sg to vpc connector * fix integ tests --- .../stack/testdata/workloads/rdws-prod.stack.yml | 3 ++- .../stack/testdata/workloads/rdws-test.stack.yml | 3 ++- .../template/templates/workloads/partials/cf/vpc-connector.yml | 3 ++- .../pkg/template/templates/workloads/services/rd-web/cf.yml | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml index c09e368d571..3703bc203ff 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml @@ -263,7 +263,8 @@ Resources: - mockSubnetID1 - mockSubnetID2 SecurityGroups: - - !Ref ServiceSecurityGroup + - !Ref ServiceSecurityGroup + - Fn::ImportValue: !Sub "${AppName}-${EnvName}-EnvironmentSecurityGroup" Tags: - Key: copilot-application Value: !Ref AppName diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml index d986cd339b3..4b586d662e5 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml @@ -336,7 +336,8 @@ Resources: - Fn::ImportValue: !Sub '${AppName}-${EnvName}-PrivateSubnets' SecurityGroups: - - !Ref ServiceSecurityGroup + - !Ref ServiceSecurityGroup + - Fn::ImportValue: !Sub "${AppName}-${EnvName}-EnvironmentSecurityGroup" Tags: - Key: copilot-application Value: !Ref AppName diff --git a/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml b/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml index 34cb48cac5a..c3c0937b225 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/vpc-connector.yml @@ -42,7 +42,8 @@ VpcConnector: !Sub '${AppName}-${EnvName}-{{.Network.SubnetsType}}' {{- end}} SecurityGroups: - - !Ref ServiceSecurityGroup + - !Ref ServiceSecurityGroup + - Fn::ImportValue: !Sub '${AppName}-${EnvName}-EnvironmentSecurityGroup' Tags: - Key: copilot-application Value: !Ref AppName diff --git a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml index 2b3a54a28e9..427162e010f 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml @@ -162,6 +162,8 @@ Resources: {{- if .Private}} AppRunnerVpcIngressConnection: + Metadata: + 'aws:copilot:description': 'The ingress connection from your environment to this service' Type: AWS::AppRunner::VpcIngressConnection Properties: ServiceArn: !GetAtt Service.ServiceArn From ed817de463aff69d5be563b5d248b261ea9ab817 Mon Sep 17 00:00:00 2001 From: danny randall <10566468+dannyrandall@users.noreply.github.com> Date: Mon, 31 Oct 2022 08:26:34 -0700 Subject: [PATCH 3/7] chore: arps describe (#4117) * get private url * gen mocks, fix some tests * fix the last remaining tests * doc for exported func * address some feedback, fix bug from bug bash * address more feedback * add error for vpc ingress not found * remove go mod replace * change to sentinel error * clarify error response --- internal/pkg/aws/apprunner/apprunner.go | 13 ++ internal/pkg/aws/apprunner/apprunner_test.go | 50 +++++ .../pkg/aws/apprunner/mocks/mock_apprunner.go | 15 ++ .../template-with-basic-manifest.yml | 3 +- .../template-with-custom-security-group.yml | 3 +- ...emplate-with-default-access-log-config.yml | 3 +- .../template-with-defaultvpc-flowlogs.yml | 3 +- ...late-with-imported-certs-observability.yml | 3 +- ...-sslpolicy-custom-empty-security-group.yml | 3 +- .../template-with-importedvpc-flowlogs.yml | 3 +- internal/pkg/describe/errors.go | 3 + internal/pkg/describe/mocks/mock_service.go | 38 +++- internal/pkg/describe/rd_web_service.go | 40 +++- internal/pkg/describe/rd_web_service_test.go | 69 +++++-- internal/pkg/describe/service.go | 61 +++++- internal/pkg/describe/service_test.go | 181 ++++++++++++++++++ internal/pkg/describe/uri.go | 14 +- internal/pkg/describe/uri_test.go | 36 +++- .../partials/environment-manager-role.yml | 3 +- .../workloads/services/rd-web/cf.yml | 9 +- 20 files changed, 499 insertions(+), 54 deletions(-) diff --git a/internal/pkg/aws/apprunner/apprunner.go b/internal/pkg/aws/apprunner/apprunner.go index 26726152796..d8f1d1f647e 100644 --- a/internal/pkg/aws/apprunner/apprunner.go +++ b/internal/pkg/aws/apprunner/apprunner.go @@ -42,6 +42,7 @@ type api interface { ResumeService(input *apprunner.ResumeServiceInput) (*apprunner.ResumeServiceOutput, error) StartDeployment(input *apprunner.StartDeploymentInput) (*apprunner.StartDeploymentOutput, error) DescribeObservabilityConfiguration(input *apprunner.DescribeObservabilityConfigurationInput) (*apprunner.DescribeObservabilityConfigurationOutput, error) + DescribeVpcIngressConnection(input *apprunner.DescribeVpcIngressConnectionInput) (*apprunner.DescribeVpcIngressConnectionOutput, error) } // AppRunner wraps an AWS AppRunner client. @@ -213,6 +214,18 @@ func (a *AppRunner) WaitForOperation(operationId, svcARN string) error { } } +// PrivateURL returns the url associated with a VPC Ingress Connection. +func (a *AppRunner) PrivateURL(vicARN string) (string, error) { + resp, err := a.client.DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{ + VpcIngressConnectionArn: aws.String(vicARN), + }) + if err != nil { + return "", fmt.Errorf("describe vpc ingress connection %q: %w", vicARN, err) + } + + return aws.StringValue(resp.VpcIngressConnection.DomainName), nil +} + // ParseServiceName returns the service name. // For example: arn:aws:apprunner:us-west-2:1234567890:service/my-service/fc1098ac269245959ba78fd58bdd4bf // will return my-service diff --git a/internal/pkg/aws/apprunner/apprunner_test.go b/internal/pkg/aws/apprunner/apprunner_test.go index 2b527b866c7..2b4bac5a34e 100644 --- a/internal/pkg/aws/apprunner/apprunner_test.go +++ b/internal/pkg/aws/apprunner/apprunner_test.go @@ -395,6 +395,56 @@ func TestAppRunner_DescribeOperation(t *testing.T) { } } +func TestAppRunner_PrivateURL(t *testing.T) { + const mockARN = "mockVicArn" + tests := map[string]struct { + mockAppRunnerClient func(m *mocks.Mockapi) + expectedErr string + expectedURL string + }{ + "error if error from sdk": { + mockAppRunnerClient: func(m *mocks.Mockapi) { + m.EXPECT().DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{ + VpcIngressConnectionArn: aws.String(mockARN), + }).Return(nil, errors.New("some error")) + }, + expectedErr: `describe vpc ingress connection "mockVicArn": some error`, + }, + "success": { + mockAppRunnerClient: func(m *mocks.Mockapi) { + m.EXPECT().DescribeVpcIngressConnection(&apprunner.DescribeVpcIngressConnectionInput{ + VpcIngressConnectionArn: aws.String(mockARN), + }).Return(&apprunner.DescribeVpcIngressConnectionOutput{ + VpcIngressConnection: &apprunner.VpcIngressConnection{ + DomainName: aws.String("example.com"), + }, + }, nil) + }, + expectedURL: "example.com", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockAppRunnerClient := mocks.NewMockapi(ctrl) + tc.mockAppRunnerClient(mockAppRunnerClient) + + service := AppRunner{ + client: mockAppRunnerClient, + } + + url, err := service.PrivateURL(mockARN) + if tc.expectedErr != "" { + require.EqualError(t, err, tc.expectedErr) + } + require.Equal(t, tc.expectedURL, url) + }) + } +} + func TestAppRunner_PauseService(t *testing.T) { const ( mockOperationId = "mock-operation" diff --git a/internal/pkg/aws/apprunner/mocks/mock_apprunner.go b/internal/pkg/aws/apprunner/mocks/mock_apprunner.go index e067be5b3cc..e00754c904d 100644 --- a/internal/pkg/aws/apprunner/mocks/mock_apprunner.go +++ b/internal/pkg/aws/apprunner/mocks/mock_apprunner.go @@ -64,6 +64,21 @@ func (mr *MockapiMockRecorder) DescribeService(input interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*Mockapi)(nil).DescribeService), input) } +// DescribeVpcIngressConnection mocks base method. +func (m *Mockapi) DescribeVpcIngressConnection(input *apprunner.DescribeVpcIngressConnectionInput) (*apprunner.DescribeVpcIngressConnectionOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeVpcIngressConnection", input) + ret0, _ := ret[0].(*apprunner.DescribeVpcIngressConnectionOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeVpcIngressConnection indicates an expected call of DescribeVpcIngressConnection. +func (mr *MockapiMockRecorder) DescribeVpcIngressConnection(input interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeVpcIngressConnection", reflect.TypeOf((*Mockapi)(nil).DescribeVpcIngressConnection), input) +} + // ListOperations mocks base method. func (m *Mockapi) ListOperations(input *apprunner.ListOperationsInput) (*apprunner.ListOperationsOutput, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml index 40fbdba78e1..042c2106f4b 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml @@ -346,7 +346,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml index 421ea110252..71ac109bfa6 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml @@ -877,7 +877,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml index 4f42f56054d..a2a347201fc 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml @@ -403,7 +403,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index 2c3d9bbbffb..4391a540d5b 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -350,7 +350,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml index e4df2b99358..a05b0fe8212 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml @@ -957,7 +957,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml index c2e9e17ed9e..f1fa420eea6 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml @@ -854,7 +854,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index 0c94b7ad79b..98bff09f4c9 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -358,7 +358,8 @@ Resources: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/describe/errors.go b/internal/pkg/describe/errors.go index 8b714127fcf..a1a17e56e11 100644 --- a/internal/pkg/describe/errors.go +++ b/internal/pkg/describe/errors.go @@ -4,6 +4,7 @@ package describe import ( + "errors" "fmt" "github.com/aws/copilot-cli/internal/pkg/template" @@ -31,3 +32,5 @@ func (err *errLBWebSvcsOnCFWithoutAlias) Error() string { return fmt.Sprintf("%s %s must have %q specified when CloudFront is enabled", english.PluralWord(len(err.services), "service", "services"), english.WordSeries(template.QuoteSliceFunc(err.services), "and"), err.aliasField) } + +var errVPCIngressConnectionNotFound = errors.New("no vpc ingress connection found") diff --git a/internal/pkg/describe/mocks/mock_service.go b/internal/pkg/describe/mocks/mock_service.go index 15b6d1e1bdc..d0e6e6e6162 100644 --- a/internal/pkg/describe/mocks/mock_service.go +++ b/internal/pkg/describe/mocks/mock_service.go @@ -242,18 +242,33 @@ func (m *MockapprunnerClient) EXPECT() *MockapprunnerClientMockRecorder { } // DescribeService mocks base method. -func (m *MockapprunnerClient) DescribeService(svcArn string) (*apprunner.Service, error) { +func (m *MockapprunnerClient) DescribeService(svcARN string) (*apprunner.Service, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DescribeService", svcArn) + ret := m.ctrl.Call(m, "DescribeService", svcARN) ret0, _ := ret[0].(*apprunner.Service) ret1, _ := ret[1].(error) return ret0, ret1 } // DescribeService indicates an expected call of DescribeService. -func (mr *MockapprunnerClientMockRecorder) DescribeService(svcArn interface{}) *gomock.Call { +func (mr *MockapprunnerClientMockRecorder) DescribeService(svcARN interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*MockapprunnerClient)(nil).DescribeService), svcArn) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*MockapprunnerClient)(nil).DescribeService), svcARN) +} + +// PrivateURL mocks base method. +func (m *MockapprunnerClient) PrivateURL(vicARN string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrivateURL", vicARN) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PrivateURL indicates an expected call of PrivateURL. +func (mr *MockapprunnerClientMockRecorder) PrivateURL(vicARN interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrivateURL", reflect.TypeOf((*MockapprunnerClient)(nil).PrivateURL), vicARN) } // MockworkloadStackDescriber is a mock of workloadStackDescriber interface. @@ -490,6 +505,21 @@ func (m *MockapprunnerDescriber) EXPECT() *MockapprunnerDescriberMockRecorder { return m.recorder } +// IsPrivate mocks base method. +func (m *MockapprunnerDescriber) IsPrivate() (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsPrivate") + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsPrivate indicates an expected call of IsPrivate. +func (mr *MockapprunnerDescriberMockRecorder) IsPrivate() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPrivate", reflect.TypeOf((*MockapprunnerDescriber)(nil).IsPrivate)) +} + // Manifest mocks base method. func (m *MockapprunnerDescriber) Manifest() ([]byte, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/describe/rd_web_service.go b/internal/pkg/describe/rd_web_service.go index 85b76c60819..6b19796d68c 100644 --- a/internal/pkg/describe/rd_web_service.go +++ b/internal/pkg/describe/rd_web_service.go @@ -75,7 +75,7 @@ func (d *RDWebServiceDescriber) Describe() (HumanJSONStringer, error) { } var observabilities []observabilityInEnv - var routes []*WebServiceRoute + var routes []*RDWSRoute var configs []*ServiceConfig var envVars envVars resources := make(map[string][]*stack.Resource) @@ -88,10 +88,22 @@ func (d *RDWebServiceDescriber) Describe() (HumanJSONStringer, error) { if err != nil { return nil, fmt.Errorf("retrieve service configuration: %w", err) } - webServiceURI := formatAppRunnerUrl(service.ServiceURL) - routes = append(routes, &WebServiceRoute{ + url, err := describer.ServiceURL() + if err != nil { + return nil, fmt.Errorf("retrieve service url: %w", err) + } + private, err := describer.IsPrivate() + if err != nil { + return nil, fmt.Errorf("check if service is private: %w", err) + } + ingress := rdwsIngressInternet + if private { + ingress = rdwsIngressEnvironment + } + routes = append(routes, &RDWSRoute{ Environment: env, - URL: webServiceURI, + URL: url, + Ingress: ingress, }) configs = append(configs, &ServiceConfig{ Environment: env, @@ -196,13 +208,27 @@ func (t *tracing) isEmpty() bool { return t == nil || t.Vendor == "" } +type rdwsIngress string + +const ( + rdwsIngressEnvironment rdwsIngress = "environment" + rdwsIngressInternet rdwsIngress = "internet" +) + +// RDWSRoute contains serialized route parameters for a Request-Driven Web Service. +type RDWSRoute struct { + Environment string `json:"environment"` + URL string `json:"url"` + Ingress rdwsIngress `json:"ingress"` +} + // rdWebSvcDesc contains serialized parameters for a web service. type rdWebSvcDesc struct { Service string `json:"service"` Type string `json:"type"` App string `json:"application"` AppRunnerConfigurations appRunnerConfigurations `json:"configurations"` - Routes []*WebServiceRoute `json:"routes"` + Routes []*RDWSRoute `json:"routes"` Variables envVars `json:"variables"` Resources deployedSvcResources `json:"resources,omitempty"` Observability observabilityPerEnv `json:"observability,omitempty"` @@ -238,11 +264,11 @@ func (w *rdWebSvcDesc) HumanString() string { } fmt.Fprint(writer, color.Bold.Sprint("\nRoutes\n\n")) writer.Flush() - headers := []string{"Environment", "URL"} + headers := []string{"Environment", "Ingress", "URL"} fmt.Fprintf(writer, " %s\n", strings.Join(headers, "\t")) fmt.Fprintf(writer, " %s\n", strings.Join(underline(headers), "\t")) for _, route := range w.Routes { - fmt.Fprintf(writer, " %s\t%s\n", route.Environment, route.URL) + fmt.Fprintf(writer, " %s\t%s\t%s\n", route.Environment, route.Ingress, route.URL) } fmt.Fprint(writer, color.Bold.Sprint("\nVariables\n\n")) diff --git a/internal/pkg/describe/rd_web_service_test.go b/internal/pkg/describe/rd_web_service_test.go index 5f1f5ada056..5b6b3cc161a 100644 --- a/internal/pkg/describe/rd_web_service_test.go +++ b/internal/pkg/describe/rd_web_service_test.go @@ -31,10 +31,10 @@ Configurations Routes - Environment URL - ----------- --- - test https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com - prod https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com + Environment Ingress URL + ----------- ------- --- + test environment https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com + prod internet https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com Variables @@ -90,12 +90,37 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { }, wantedError: fmt.Errorf("retrieve service configuration: some error"), }, + "return error if fail to get service url": { + shouldOutputResources: true, + setupMocks: func(m apprunnerSvcDescriberMocks) { + gomock.InOrder( + m.storeSvc.EXPECT().ListEnvironmentsDeployedTo(testApp, testSvc).Return([]string{testEnv}, nil), + m.ecsSvcDescriber.EXPECT().Service().Return(&apprunner.Service{}, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("", mockErr), + ) + }, + wantedError: fmt.Errorf("retrieve service url: some error"), + }, + "return error if fail to check if private": { + shouldOutputResources: true, + setupMocks: func(m apprunnerSvcDescriberMocks) { + gomock.InOrder( + m.storeSvc.EXPECT().ListEnvironmentsDeployedTo(testApp, testSvc).Return([]string{testEnv}, nil), + m.ecsSvcDescriber.EXPECT().Service().Return(&apprunner.Service{}, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, mockErr), + ) + }, + wantedError: fmt.Errorf("check if service is private: some error"), + }, "return error if fail to retrieve service resources": { shouldOutputResources: true, setupMocks: func(m apprunnerSvcDescriberMocks) { gomock.InOrder( m.storeSvc.EXPECT().ListEnvironmentsDeployedTo(testApp, testSvc).Return([]string{testEnv}, nil), m.ecsSvcDescriber.EXPECT().Service().Return(&apprunner.Service{}, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, nil), m.ecsSvcDescriber.EXPECT().ServiceStackResources().Return(nil, mockErr), ) }, @@ -119,6 +144,8 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { }, }, }, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(true, nil), m.ecsSvcDescriber.EXPECT().ServiceStackResources().Return([]*stack.Resource{ { Type: "AWS::AppRunner::Service", @@ -138,6 +165,8 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { }, }, }, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, nil), m.ecsSvcDescriber.EXPECT().ServiceStackResources().Return([]*stack.Resource{ { Type: "AWS::AppRunner::Service", @@ -164,14 +193,16 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { Port: "80", }, }, - Routes: []*WebServiceRoute{ + Routes: []*RDWSRoute{ { Environment: "test", URL: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressEnvironment, }, { Environment: "prod", URL: "https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressInternet, }, }, Variables: []*envVar{ @@ -226,6 +257,8 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { }, }, }, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(true, nil), m.ecsSvcDescriber.EXPECT().ServiceStackResources().Return([]*stack.Resource{ { Type: "AWS::AppRunner::Service", @@ -245,6 +278,8 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { }, }, }, nil), + m.ecsSvcDescriber.EXPECT().ServiceURL().Return("https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, nil), m.ecsSvcDescriber.EXPECT().ServiceStackResources().Return([]*stack.Resource{ { Type: "AWS::AppRunner::Service", @@ -271,14 +306,16 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { Port: "80", }, }, - Routes: []*WebServiceRoute{ + Routes: []*RDWSRoute{ { Environment: "test", URL: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressEnvironment, }, { Environment: "prod", URL: "https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressInternet, }, }, Variables: []*envVar{ @@ -362,7 +399,7 @@ func TestRDWebServiceDescriber_Describe(t *testing.T) { func TestRDWebServiceDesc_String(t *testing.T) { t.Run("correct output including resources", func(t *testing.T) { wantedHumanString := humanStringWithResources - wantedJSONString := "{\"service\":\"testsvc\",\"type\":\"Request-Driven Web Service\",\"application\":\"testapp\",\"configurations\":[{\"environment\":\"test\",\"port\":\"80\",\"cpu\":\"1024\",\"memory\":\"2048\"},{\"environment\":\"prod\",\"port\":\"80\",\"cpu\":\"2048\",\"memory\":\"3072\"}],\"routes\":[{\"environment\":\"test\",\"url\":\"https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com\"},{\"environment\":\"prod\",\"url\":\"https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com\"}],\"variables\":[{\"environment\":\"prod\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"prod\"},{\"environment\":\"test\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"test\"}],\"resources\":{\"prod\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-prod-testsvc\"}],\"test\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-test-testsvc\"}]}}\n" + wantedJSONString := "{\"service\":\"testsvc\",\"type\":\"Request-Driven Web Service\",\"application\":\"testapp\",\"configurations\":[{\"environment\":\"test\",\"port\":\"80\",\"cpu\":\"1024\",\"memory\":\"2048\"},{\"environment\":\"prod\",\"port\":\"80\",\"cpu\":\"2048\",\"memory\":\"3072\"}],\"routes\":[{\"environment\":\"test\",\"url\":\"https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com\",\"ingress\":\"environment\"},{\"environment\":\"prod\",\"url\":\"https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com\",\"ingress\":\"internet\"}],\"variables\":[{\"environment\":\"prod\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"prod\"},{\"environment\":\"test\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"test\"}],\"resources\":{\"prod\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-prod-testsvc\"}],\"test\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-test-testsvc\"}]}}\n" svcDesc := &rdWebSvcDesc{ Service: "testsvc", Type: "Request-Driven Web Service", @@ -381,14 +418,16 @@ func TestRDWebServiceDesc_String(t *testing.T) { Port: "80", }, }, - Routes: []*WebServiceRoute{ + Routes: []*RDWSRoute{ { Environment: "test", URL: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressEnvironment, }, { Environment: "prod", URL: "https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressInternet, }, }, Variables: []*envVar{ @@ -449,10 +488,10 @@ Observability Routes - Environment URL - ----------- --- - test https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com - prod https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com + Environment Ingress URL + ----------- ------- --- + test environment https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com + prod internet https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com Variables @@ -469,7 +508,7 @@ Resources prod AWS::AppRunner::Service arn:aws:apprunner:us-east-1:111111111111:service/testapp-prod-testsvc ` - wantedJSONString := "{\"service\":\"testsvc\",\"type\":\"Request-Driven Web Service\",\"application\":\"testapp\",\"configurations\":[{\"environment\":\"test\",\"port\":\"80\",\"cpu\":\"1024\",\"memory\":\"2048\"},{\"environment\":\"prod\",\"port\":\"80\",\"cpu\":\"2048\",\"memory\":\"3072\"}],\"routes\":[{\"environment\":\"test\",\"url\":\"https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com\"},{\"environment\":\"prod\",\"url\":\"https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com\"}],\"variables\":[{\"environment\":\"prod\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"prod\"},{\"environment\":\"test\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"test\"}],\"resources\":{\"prod\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-prod-testsvc\"}],\"test\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-test-testsvc\"}]},\"observability\":[{\"environment\":\"test\",\"tracing\":{\"vendor\":\"mockVendor\"}},{\"environment\":\"prod\"}]}\n" + wantedJSONString := "{\"service\":\"testsvc\",\"type\":\"Request-Driven Web Service\",\"application\":\"testapp\",\"configurations\":[{\"environment\":\"test\",\"port\":\"80\",\"cpu\":\"1024\",\"memory\":\"2048\"},{\"environment\":\"prod\",\"port\":\"80\",\"cpu\":\"2048\",\"memory\":\"3072\"}],\"routes\":[{\"environment\":\"test\",\"url\":\"https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com\",\"ingress\":\"environment\"},{\"environment\":\"prod\",\"url\":\"https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com\",\"ingress\":\"internet\"}],\"variables\":[{\"environment\":\"prod\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"prod\"},{\"environment\":\"test\",\"name\":\"COPILOT_ENVIRONMENT_NAME\",\"value\":\"test\"}],\"resources\":{\"prod\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-prod-testsvc\"}],\"test\":[{\"type\":\"AWS::AppRunner::Service\",\"physicalID\":\"arn:aws:apprunner:us-east-1:111111111111:service/testapp-test-testsvc\"}]},\"observability\":[{\"environment\":\"test\",\"tracing\":{\"vendor\":\"mockVendor\"}},{\"environment\":\"prod\"}]}\n" svcDesc := &rdWebSvcDesc{ Service: "testsvc", Type: "Request-Driven Web Service", @@ -488,14 +527,16 @@ Resources Port: "80", }, }, - Routes: []*WebServiceRoute{ + Routes: []*RDWSRoute{ { Environment: "test", URL: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressEnvironment, }, { Environment: "prod", URL: "https://tumkjmvjjf.public.us-east-1.apprunner.amazonaws.com", + Ingress: rdwsIngressInternet, }, }, Variables: []*envVar{ diff --git a/internal/pkg/describe/service.go b/internal/pkg/describe/service.go index 2484554741c..dcc0240e7f5 100644 --- a/internal/pkg/describe/service.go +++ b/internal/pkg/describe/service.go @@ -4,6 +4,7 @@ package describe import ( + "errors" "fmt" "io" "net/url" @@ -28,7 +29,10 @@ const ( waitConditionHandle = "AWS::CloudFormation::WaitConditionHandle" ) -const apprunnerServiceType = "AWS::AppRunner::Service" +const ( + apprunnerServiceType = "AWS::AppRunner::Service" + apprunnerVPCIngressConnectionType = "AWS::AppRunner::VpcIngressConnection" +) // ConfigStoreSvc wraps methods of config store. type ConfigStoreSvc interface { @@ -51,7 +55,8 @@ type ecsClient interface { } type apprunnerClient interface { - DescribeService(svcArn string) (*apprunner.Service, error) + DescribeService(svcARN string) (*apprunner.Service, error) + PrivateURL(vicARN string) (string, error) } type workloadStackDescriber interface { @@ -75,6 +80,7 @@ type apprunnerDescriber interface { Service() (*apprunner.Service, error) ServiceARN() (string, error) ServiceURL() (string, error) + IsPrivate() (bool, error) } // serviceStackDescriber provides base functionality for retrieving info about a service. @@ -280,6 +286,24 @@ func (d *appRunnerServiceDescriber) ServiceARN() (string, error) { return "", fmt.Errorf("no App Runner Service in service stack") } +// vpcIngressConnectionARN returns the ARN of the VPC Ingress Connection +// for this service. If one does not exist, it returns errVPCIngressConnectionNotFound. +func (d *appRunnerServiceDescriber) vpcIngressConnectionARN() (string, error) { + serviceStackResources, err := d.ServiceStackResources() + if err != nil { + return "", err + } + + for _, resource := range serviceStackResources { + arn := resource.PhysicalID + if resource.Type == apprunnerVPCIngressConnectionType && arn != "" { + return arn, nil + } + } + + return "", errVPCIngressConnectionNotFound +} + // Service retrieves an app runner service. func (d *appRunnerServiceDescriber) Service() (*apprunner.Service, error) { serviceARN, err := d.ServiceARN() @@ -294,17 +318,44 @@ func (d *appRunnerServiceDescriber) Service() (*apprunner.Service, error) { return service, nil } +// IsPrivate returns true if the service is configured as non-public. +func (d *appRunnerServiceDescriber) IsPrivate() (bool, error) { + _, err := d.vpcIngressConnectionARN() + if err != nil { + if errors.Is(err, errVPCIngressConnectionNotFound) { + return false, nil + } + + return false, err + } + + return true, nil +} + // ServiceURL retrieves the app runner service URL. func (d *appRunnerServiceDescriber) ServiceURL() (string, error) { + vicARN, err := d.vpcIngressConnectionARN() + isVICNotFound := errors.Is(err, errVPCIngressConnectionNotFound) + if err != nil && !isVICNotFound { + return "", err + } + + if !isVICNotFound { + url, err := d.apprunnerClient.PrivateURL(vicARN) + if err != nil { + return "", err + } + return formatAppRunnerURL(url), nil + } + service, err := d.Service() if err != nil { return "", err } - - return formatAppRunnerUrl(service.ServiceURL), nil + return formatAppRunnerURL(service.ServiceURL), nil } -func formatAppRunnerUrl(serviceURL string) string { +func formatAppRunnerURL(serviceURL string) string { svcUrl := &url.URL{ Host: serviceURL, // App Runner defaults to https diff --git a/internal/pkg/describe/service_test.go b/internal/pkg/describe/service_test.go index d14e807cccd..c0a475d4893 100644 --- a/internal/pkg/describe/service_test.go +++ b/internal/pkg/describe/service_test.go @@ -11,6 +11,7 @@ import ( ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/copilot-cli/internal/pkg/aws/apprunner" "github.com/aws/copilot-cli/internal/pkg/aws/ecs" awsecs "github.com/aws/copilot-cli/internal/pkg/aws/ecs" "github.com/aws/copilot-cli/internal/pkg/describe/mocks" @@ -553,3 +554,183 @@ func TestServiceDescriber_Platform(t *testing.T) { }) } } + +type apprunnerMocks struct { + apprunnerClient *mocks.MockapprunnerClient + stackDescriber *mocks.MockstackDescriber +} + +func TestAppRunnerServiceDescriber_ServiceURL(t *testing.T) { + mockErr := errors.New("some error") + mockVICARN := "mockVICARN" + mockServiceARN := "mockServiceARN" + tests := map[string]struct { + setupMocks func(m apprunnerMocks) + + expected string + expectedErr string + }{ + "get ingress connection error": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return(nil, mockErr) + }, + expectedErr: "some error", + }, + "get private url error": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: apprunnerVPCIngressConnectionType, + PhysicalID: mockVICARN, + }, + }, nil) + m.apprunnerClient.EXPECT().PrivateURL(mockVICARN).Return("", mockErr) + }, + expectedErr: "some error", + }, + "private service, success": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: apprunnerVPCIngressConnectionType, + PhysicalID: mockVICARN, + }, + }, nil) + m.apprunnerClient.EXPECT().PrivateURL(mockVICARN).Return("example.com", nil) + }, + expected: "https://example.com", + }, + "public service, resources fails": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return(nil, nil) + m.stackDescriber.EXPECT().Resources().Return(nil, mockErr) + }, + expectedErr: "some error", + }, + "public service, no app runner resource": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: "random", + PhysicalID: "random", + }, + }, nil) + }, + expectedErr: "no App Runner Service in service stack", + }, + "public service, describe service fails": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: apprunnerServiceType, + PhysicalID: mockServiceARN, + }, + }, nil) + m.apprunnerClient.EXPECT().DescribeService(mockServiceARN).Return(nil, mockErr) + }, + expectedErr: "describe service: some error", + }, + "public service, success": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: apprunnerServiceType, + PhysicalID: mockServiceARN, + }, + }, nil) + m.apprunnerClient.EXPECT().DescribeService(mockServiceARN).Return(&apprunner.Service{ + ServiceURL: "example.com", + }, nil) + }, + expected: "https://example.com", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + m := apprunnerMocks{ + apprunnerClient: mocks.NewMockapprunnerClient(ctrl), + stackDescriber: mocks.NewMockstackDescriber(ctrl), + } + tc.setupMocks(m) + + d := &appRunnerServiceDescriber{ + serviceStackDescriber: &serviceStackDescriber{ + cfn: m.stackDescriber, + }, + apprunnerClient: m.apprunnerClient, + } + + url, err := d.ServiceURL() + if tc.expectedErr != "" { + require.EqualError(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, url) + } + }) + } +} + +func TestAppRunnerServiceDescriber_IsPrivate(t *testing.T) { + mockErr := errors.New("some error") + tests := map[string]struct { + setupMocks func(m apprunnerMocks) + + expected bool + expectedErr string + }{ + "get resources error": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return(nil, mockErr) + }, + expectedErr: "some error", + }, + "is not private": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return(nil, nil) + }, + expected: false, + }, + "is private": { + setupMocks: func(m apprunnerMocks) { + m.stackDescriber.EXPECT().Resources().Return([]*stack.Resource{ + { + Type: apprunnerVPCIngressConnectionType, + PhysicalID: "arn", + }, + }, nil) + }, + expected: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + m := apprunnerMocks{ + stackDescriber: mocks.NewMockstackDescriber(ctrl), + } + tc.setupMocks(m) + + d := &appRunnerServiceDescriber{ + serviceStackDescriber: &serviceStackDescriber{ + cfn: m.stackDescriber, + }, + } + + isPrivate, err := d.IsPrivate() + if tc.expectedErr != "" { + require.EqualError(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, isPrivate) + } + }) + } +} diff --git a/internal/pkg/describe/uri.go b/internal/pkg/describe/uri.go index b9e4bd47529..6195296c575 100644 --- a/internal/pkg/describe/uri.go +++ b/internal/pkg/describe/uri.go @@ -315,12 +315,22 @@ func (d *RDWebServiceDescriber) URI(envName string) (URI, error) { serviceURL, err := describer.ServiceURL() if err != nil { - return URI{}, fmt.Errorf("get outputs for service %s: %w", d.svc, err) + return URI{}, fmt.Errorf("get outputs for service %q: %w", d.svc, err) + } + + isPrivate, err := describer.IsPrivate() + if err != nil { + return URI{}, fmt.Errorf("check if service %q is private: %w", d.svc, err) + } + + accessType := URIAccessTypeInternet + if isPrivate { + accessType = URIAccessTypeInternal } return URI{ URI: serviceURL, - AccessType: URIAccessTypeInternet, + AccessType: accessType, }, nil } diff --git a/internal/pkg/describe/uri_test.go b/internal/pkg/describe/uri_test.go index 1c4450ab91b..cc98c9cf9c2 100644 --- a/internal/pkg/describe/uri_test.go +++ b/internal/pkg/describe/uri_test.go @@ -492,7 +492,7 @@ func TestRDWebServiceDescriber_URI(t *testing.T) { testCases := map[string]struct { setupMocks func(mocks apprunnerSvcDescriberMocks) - wantedURI string + wantedURI URI wantedError error }{ "fail to get outputs of service stack": { @@ -501,16 +501,40 @@ func TestRDWebServiceDescriber_URI(t *testing.T) { m.ecsSvcDescriber.EXPECT().ServiceURL().Return("", mockErr), ) }, - wantedError: fmt.Errorf("get outputs for service frontend: some error"), + wantedError: fmt.Errorf(`get outputs for service "frontend": some error`), }, - "succeed in getting outputs of service stack": { + "fail to check if private": { setupMocks: func(m apprunnerSvcDescriberMocks) { gomock.InOrder( m.ecsSvcDescriber.EXPECT().ServiceURL().Return(testSvcURL, nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, mockErr), ) }, - - wantedURI: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + wantedError: fmt.Errorf(`check if service "frontend" is private: some error`), + }, + "succeed in getting public service uri": { + setupMocks: func(m apprunnerSvcDescriberMocks) { + gomock.InOrder( + m.ecsSvcDescriber.EXPECT().ServiceURL().Return(testSvcURL, nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(false, nil), + ) + }, + wantedURI: URI{ + URI: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + AccessType: URIAccessTypeInternet, + }, + }, + "succeed in getting private service uri": { + setupMocks: func(m apprunnerSvcDescriberMocks) { + gomock.InOrder( + m.ecsSvcDescriber.EXPECT().ServiceURL().Return(testSvcURL, nil), + m.ecsSvcDescriber.EXPECT().IsPrivate().Return(true, nil), + ) + }, + wantedURI: URI{ + URI: "https://6znxd4ra33.public.us-east-1.apprunner.amazonaws.com", + AccessType: URIAccessTypeInternal, + }, }, } @@ -541,7 +565,7 @@ func TestRDWebServiceDescriber_URI(t *testing.T) { require.EqualError(t, err, tc.wantedError.Error()) } else { require.NoError(t, err) - require.Equal(t, tc.wantedURI, actual.URI) + require.Equal(t, tc.wantedURI, actual) } }) } diff --git a/internal/pkg/template/templates/environment/partials/environment-manager-role.yml b/internal/pkg/template/templates/environment/partials/environment-manager-role.yml index 39f0c89d4d2..380692d9f06 100644 --- a/internal/pkg/template/templates/environment/partials/environment-manager-role.yml +++ b/internal/pkg/template/templates/environment/partials/environment-manager-role.yml @@ -265,7 +265,8 @@ EnvironmentManagerRole: "apprunner:PauseService", "apprunner:ResumeService", "apprunner:StartDeployment", - "apprunner:DescribeObservabilityConfiguration" + "apprunner:DescribeObservabilityConfiguration", + "apprunner:DescribeVpcIngressConnection" ] Resource: "*" - Sid: Tags diff --git a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml index 427162e010f..a4a67f0da7c 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml @@ -265,11 +265,4 @@ Resources: {{- if requiresVPCConnector .}} {{include "vpc-connector" . | indent 2 }} -{{- end }} - -{{- if .Private}} -# TODO remove after beta SDK works -Outputs: - DomainName: - Value: !GetAtt AppRunnerVpcIngressConnection.DomainName -{{- end}} \ No newline at end of file +{{- end }} \ No newline at end of file From deef40d489ce6b60ac75416ebb42d7ec269a8b9e Mon Sep 17 00:00:00 2001 From: danny randall <10566468+dannyrandall@users.noreply.github.com> Date: Mon, 31 Oct 2022 09:48:03 -0700 Subject: [PATCH 4/7] chore: arps `svc init` prompt (#4118) * add new prompt for private services * some test cases * update new fields * address some feedback * add flag tests, list options for flag, add validation to ingress type * address feedback from @efekarakus * nit --- internal/pkg/cli/flag.go | 6 ++ internal/pkg/cli/svc_init.go | 55 +++++++++++- internal/pkg/cli/svc_init_test.go | 84 ++++++++++++++++++- internal/pkg/initialize/workload.go | 2 + internal/pkg/manifest/rd_web_svc.go | 5 ++ internal/pkg/manifest/union.go | 4 +- .../workloads/services/rd-web/manifest.yml | 20 ++++- 7 files changed, 170 insertions(+), 6 deletions(-) diff --git a/internal/pkg/cli/flag.go b/internal/pkg/cli/flag.go index f5b9f9cc90c..07ca3f3db84 100644 --- a/internal/pkg/cli/flag.go +++ b/internal/pkg/cli/flag.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/aws/copilot-cli/internal/pkg/manifest" + "github.com/dustin/go-humanize/english" ) // Long flag names. @@ -64,6 +65,8 @@ const ( noSubscriptionFlag = "no-subscribe" subscribeTopicsFlag = "subscribe-topics" + ingressTypeFlag = "ingress-type" + storageTypeFlag = "storage-type" storagePartitionKeyFlag = "partition-key" storageSortKeyFlag = "sort-key" @@ -199,6 +202,9 @@ Mutually exclusive with the -%s ,--%s and --%s flags.`, nameFlagShort, nameFlag, repoURLFlagDescription = fmt.Sprintf(`The repository URL to trigger your pipeline. Supported providers are: %s.`, strings.Join(manifest.PipelineProviders, ", ")) + + ingressTypeFlagDescription = fmt.Sprintf(`Required for a Request-Driven Web Service. Allowed source of traffic to your service. +Must be one of %s`, english.OxfordWordSeries(rdwsIngressOptions, "or")) ) const ( diff --git a/internal/pkg/cli/svc_init.go b/internal/pkg/cli/svc_init.go index 162bf0aedfb..3e54f7fb9b4 100644 --- a/internal/pkg/cli/svc_init.go +++ b/internal/pkg/cli/svc_init.go @@ -8,9 +8,11 @@ import ( "fmt" "os" "strconv" + "strings" "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/copilot-cli/internal/pkg/aws/identity" + "github.com/dustin/go-humanize/english" "github.com/aws/aws-sdk-go/aws" "github.com/aws/copilot-cli/internal/pkg/docker/dockerfile" @@ -48,7 +50,7 @@ const ( var ( fmtSvcInitSvcTypePrompt = "Which %s best represents your service's architecture?" - svcInitSvcTypeHelpPrompt = fmt.Sprintf(`A %s is an internet-facing HTTP server managed by AWS App Runner that scales based on incoming requests. + svcInitSvcTypeHelpPrompt = fmt.Sprintf(`A %s is an internet-facing or private HTTP server managed by AWS App Runner that scales based on incoming requests. To learn more see: https://git.io/JEEfb A %s is an internet-facing HTTP server managed by Amazon ECS on AWS Fargate behind a load balancer. @@ -82,9 +84,23 @@ You should set this to the port which your Dockerfile uses to communicate with t svcInitPublisherHelpPrompt = `A publisher is an existing SNS Topic to which a service publishes messages. These messages can be consumed by the Worker Service.` + svcInitIngressTypePrompt = "Would you like to accept traffic from your environment or the internet?" + svcInitIngressTypeHelpPrompt = `"Environment" will configure your service as private. +"Internet" will configure your service as public.` + wkldInitImagePrompt = fmt.Sprintf("What's the %s ([registry/]repository[:tag|@digest]) of the image to use?", color.Emphasize("location")) ) +const ( + ingressTypeEnvironment = "Environment" + ingressTypeInternet = "Internet" +) + +var rdwsIngressOptions = []string{ + ingressTypeEnvironment, + ingressTypeInternet, +} + var serviceTypeHints = map[string]string{ manifest.RequestDrivenWebServiceType: "App Runner", manifest.LoadBalancedWebServiceType: "Internet to ECS on Fargate", @@ -100,6 +116,7 @@ type initWkldVars struct { image string subscriptions []string noSubscribe bool + ingressType string } type initSvcVars struct { @@ -215,6 +232,9 @@ func (o *initSvcOpts) Validate() error { if err := validateSubscribe(o.noSubscribe, o.subscriptions); err != nil { return err } + if err := o.validateIngressType(); err != nil { + return err + } return nil } @@ -253,6 +273,9 @@ func (o *initSvcOpts) Ask() error { if err := o.validateSvc(); err != nil { return err } + if err := o.askIngressType(); err != nil { + return err + } shouldSkipAsking, err := o.shouldSkipAsking() if err != nil { return err @@ -313,6 +336,7 @@ func (o *initSvcOpts) Execute() error { }, Port: o.port, HealthCheck: hc, + Private: strings.EqualFold(o.ingressType, ingressTypeEnvironment), }) if err != nil { return err @@ -389,6 +413,34 @@ func (o *initSvcOpts) askSvcName() error { return nil } +func (o *initSvcOpts) askIngressType() error { + if o.wkldType != manifest.RequestDrivenWebServiceType || o.ingressType != "" { + return nil + } + + var opts []prompt.Option + for _, typ := range rdwsIngressOptions { + opts = append(opts, prompt.Option{Value: typ}) + } + + t, err := o.prompt.SelectOption(svcInitIngressTypePrompt, svcInitIngressTypeHelpPrompt, opts, prompt.WithFinalMessage("Reachable from:")) + if err != nil { + return fmt.Errorf("select ingress type: %w", err) + } + o.ingressType = t + return nil +} + +func (o *initSvcOpts) validateIngressType() error { + if o.wkldType != manifest.RequestDrivenWebServiceType { + return nil + } + if strings.EqualFold(o.ingressType, "internet") || strings.EqualFold(o.ingressType, "environment") { + return nil + } + return fmt.Errorf("invalid ingress type %q: must be one of %s", o.ingressType, english.OxfordWordSeries(rdwsIngressOptions, "or")) +} + func (o *initSvcOpts) askImage() error { if o.image != "" { return nil @@ -712,6 +764,7 @@ This command is also run as part of "copilot init".`, cmd.Flags().Uint16Var(&vars.port, svcPortFlag, 0, svcPortFlagDescription) cmd.Flags().StringArrayVar(&vars.subscriptions, subscribeTopicsFlag, []string{}, subscribeTopicsFlagDescription) cmd.Flags().BoolVar(&vars.noSubscribe, noSubscriptionFlag, false, noSubscriptionFlagDescription) + cmd.Flags().StringVar(&vars.ingressType, ingressTypeFlag, "", ingressTypeFlagDescription) return cmd } diff --git a/internal/pkg/cli/svc_init_test.go b/internal/pkg/cli/svc_init_test.go index 2fd9912ed36..6267f75cbbf 100644 --- a/internal/pkg/cli/svc_init_test.go +++ b/internal/pkg/cli/svc_init_test.go @@ -45,6 +45,7 @@ func TestSvcInitOpts_Validate(t *testing.T) { inSvcPort uint16 inSubscribeTags []string inNoSubscribe bool + inIngressType string setupMocks func(mocks initSvcMocks) mockFileSystem func(mockFS afero.Fs) @@ -103,6 +104,21 @@ func TestSvcInitOpts_Validate(t *testing.T) { }, wantedErr: errors.New("validate subscribe configuration: cannot specify both --no-subscribe and --subscribe-topics"), }, + "rdws invalid ingress type error": { + inSvcName: "frontend", + inSvcType: "Request-Driven Web Service", + inDockerfilePath: "./hello/Dockerfile", + inIngressType: "invalid", + + setupMocks: func(m initSvcMocks) { + m.mockStore.EXPECT().GetApplication("phonetool").Return(&config.Application{}, nil) + }, + mockFileSystem: func(mockFS afero.Fs) { + mockFS.MkdirAll("hello", 0755) + afero.WriteFile(mockFS, "hello/Dockerfile", []byte("FROM nginx"), 0644) + }, + wantedErr: errors.New(`invalid ingress type "invalid": must be one of Environment or Internet`), + }, "valid flags": { inSvcName: "frontend", inSvcType: "Load Balanced Web Service", @@ -115,7 +131,20 @@ func TestSvcInitOpts_Validate(t *testing.T) { mockFS.MkdirAll("hello", 0755) afero.WriteFile(mockFS, "hello/Dockerfile", []byte("FROM nginx"), 0644) }, - wantedErr: nil, + }, + "valid rdws flags": { + inSvcName: "frontend", + inSvcType: "Request-Driven Web Service", + inDockerfilePath: "./hello/Dockerfile", + inIngressType: "Internet", + + setupMocks: func(m initSvcMocks) { + m.mockStore.EXPECT().GetApplication("phonetool").Return(&config.Application{}, nil) + }, + mockFileSystem: func(mockFS afero.Fs) { + mockFS.MkdirAll("hello", 0755) + afero.WriteFile(mockFS, "hello/Dockerfile", []byte("FROM nginx"), 0644) + }, }, } @@ -141,6 +170,7 @@ func TestSvcInitOpts_Validate(t *testing.T) { appName: tc.inAppName, subscriptions: tc.inSubscribeTags, noSubscribe: tc.inNoSubscribe, + ingressType: tc.inIngressType, }, port: tc.inSvcPort, }, @@ -186,6 +216,7 @@ func TestSvcInitOpts_Ask(t *testing.T) { inSvcPort uint16 inSubscribeTags []string inNoSubscribe bool + inIngressType string setupMocks func(mocks initSvcMocks) @@ -354,6 +385,56 @@ type: Request-Driven Web Service`), nil) }, wantedErr: fmt.Errorf("service name iamoverfortycharacterlongandaninvalidrdwsname is invalid: value must not exceed 40 characters"), }, + "rdws prompt for ingress type": { + inSvcType: appRunnerSvcType, + inSvcName: wantedSvcName, + inSvcPort: wantedSvcPort, + inDockerfilePath: wantedDockerfilePath, + + setupMocks: func(m initSvcMocks) { + m.mockStore.EXPECT().GetService(mockAppName, wantedSvcName).Return(nil, &config.ErrNoSuchService{}) + m.mockMftReader.EXPECT().ReadWorkloadManifest(wantedSvcName).Return(nil, &workspace.ErrFileNotExists{FileName: wantedSvcName}) + m.mockPrompt.EXPECT().SelectOption(gomock.Eq(svcInitIngressTypePrompt), gomock.Any(), gomock.Eq([]prompt.Option{ + { + Value: "Environment", + }, + { + Value: "Internet", + }, + }), gomock.Any()).Return("Environment", nil) + }, + }, + "rdws prompt for ingress type error": { + inSvcType: appRunnerSvcType, + inSvcName: wantedSvcName, + inSvcPort: wantedSvcPort, + inDockerfilePath: wantedDockerfilePath, + + setupMocks: func(m initSvcMocks) { + m.mockStore.EXPECT().GetService(mockAppName, wantedSvcName).Return(nil, &config.ErrNoSuchService{}) + m.mockPrompt.EXPECT().SelectOption(gomock.Eq(svcInitIngressTypePrompt), gomock.Any(), gomock.Eq([]prompt.Option{ + { + Value: "Environment", + }, + { + Value: "Internet", + }, + }), gomock.Any()).Return("", errors.New("some error")) + }, + wantedErr: errors.New("select ingress type: some error"), + }, + "rdws skip ingress type prompt with flag": { + inSvcType: appRunnerSvcType, + inSvcName: wantedSvcName, + inSvcPort: wantedSvcPort, + inDockerfilePath: wantedDockerfilePath, + inIngressType: ingressTypeInternet, + + setupMocks: func(m initSvcMocks) { + m.mockStore.EXPECT().GetService(mockAppName, wantedSvcName).Return(nil, &config.ErrNoSuchService{}) + m.mockMftReader.EXPECT().ReadWorkloadManifest(wantedSvcName).Return(nil, &workspace.ErrFileNotExists{FileName: wantedSvcName}) + }, + }, "skip selecting Dockerfile if image flag is set": { inSvcType: wantedSvcType, inSvcName: wantedSvcName, @@ -647,6 +728,7 @@ type: Request-Driven Web Service`), nil) dockerfilePath: tc.inDockerfilePath, noSubscribe: tc.inNoSubscribe, subscriptions: tc.inSubscribeTags, + ingressType: tc.inIngressType, appName: mockAppName, }, port: tc.inSvcPort, diff --git a/internal/pkg/initialize/workload.go b/internal/pkg/initialize/workload.go index 2ef4c6d822c..acb4600c3cc 100644 --- a/internal/pkg/initialize/workload.go +++ b/internal/pkg/initialize/workload.go @@ -84,6 +84,7 @@ type ServiceProps struct { WorkloadProps Port uint16 HealthCheck manifest.ContainerHealthCheck + Private bool appDomain *string } @@ -344,6 +345,7 @@ func (w *WorkloadInitializer) newRequestDrivenWebServiceManifest(i *ServiceProps }, Port: i.Port, Platform: i.Platform, + Private: i.Private, } return manifest.NewRequestDrivenWebService(props) } diff --git a/internal/pkg/manifest/rd_web_svc.go b/internal/pkg/manifest/rd_web_svc.go index 45a604bdb51..b508e2bf7f9 100644 --- a/internal/pkg/manifest/rd_web_svc.go +++ b/internal/pkg/manifest/rd_web_svc.go @@ -108,6 +108,7 @@ type RequestDrivenWebServiceProps struct { *WorkloadProps Port uint16 Platform PlatformArgsOrString + Private bool } // NewRequestDrivenWebService creates a new Request-Driven Web Service manifest with default values. @@ -118,6 +119,10 @@ func NewRequestDrivenWebService(props *RequestDrivenWebServiceProps) *RequestDri svc.RequestDrivenWebServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) svc.RequestDrivenWebServiceConfig.ImageConfig.Port = aws.Uint16(props.Port) svc.RequestDrivenWebServiceConfig.InstanceConfig.Platform = props.Platform + if props.Private { + svc.Private = BasicToUnion[bool, VPCEndpoint](true) + svc.Network.VPC.Placement.PlacementString = (*PlacementString)(aws.String("private")) + } svc.parser = template.New() return svc } diff --git a/internal/pkg/manifest/union.go b/internal/pkg/manifest/union.go index e37545f2cb7..719938be56f 100644 --- a/internal/pkg/manifest/union.go +++ b/internal/pkg/manifest/union.go @@ -52,12 +52,12 @@ func AdvancedToUnion[Basic, Advanced any](val Advanced) Union[Basic, Advanced] { } // IsBasic returns true if the underlying value of t is type Basic. -func (t *Union[_, _]) IsBasic() bool { +func (t Union[_, _]) IsBasic() bool { return t.isBasic } // IsAdvanced returns true if the underlying value of t is type Advanced. -func (t *Union[_, _]) IsAdvanced() bool { +func (t Union[_, _]) IsAdvanced() bool { return t.isAdvanced } diff --git a/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml b/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml index 64aef1fcda0..6aa96ec3168 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml @@ -19,7 +19,16 @@ image: {{- end}} # Port exposed through your container to route traffic to it. port: {{.ImageConfig.Port}} - +{{if .Private.IsBasic}} +http: + private: {{.Private.Basic}} + # healthcheck: + # path: / + # healthy_threshold: 3 + # unhealthy_threshold: 5 + # interval: 10s + # timeout: 5s +{{- else}} # http: # healthcheck: # path: / @@ -27,16 +36,23 @@ image: # unhealthy_threshold: 5 # interval: 10s # timeout: 5s +{{- end}} # Number of CPU units for the task. cpu: {{.InstanceConfig.CPU}} # Amount of memory in MiB used by the task. memory: {{.InstanceConfig.Memory}} - +{{if .Network.VPC.Placement.PlacementString}} +# Connect your App Runner service to your environment's VPC. +network: + vpc: + placement: {{.Network.VPC.Placement.PlacementString}} +{{else}} # # Connect your App Runner service to your environment's VPC. # network: # vpc: # placement: private +{{end}} # Enable tracing for the service. # observability: From ced43d4d38aec25734548f86273bc393bf43f79d Mon Sep 17 00:00:00 2001 From: danny randall <10566468+dannyrandall@users.noreply.github.com> Date: Mon, 31 Oct 2022 11:56:07 -0700 Subject: [PATCH 5/7] chore: don't create VPC Endpoint if endpoint specified (#4136) * chore: don't create VPC Endpoint if endpoint specified * add env controller test --- internal/pkg/template/workload.go | 2 +- internal/pkg/template/workload_test.go | 87 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go index 47941945517..1fdda7378a5 100644 --- a/internal/pkg/template/workload.go +++ b/internal/pkg/template/workload.go @@ -735,7 +735,7 @@ func envControllerParameters(o WorkloadOpts) []string { } } if o.WorkloadType == "Request-Driven Web Service" { - if o.Private { + if o.Private && o.AppRunnerVPCEndpoint == nil { parameters = append(parameters, "AppRunnerPrivateWorkloads,") } } diff --git a/internal/pkg/template/workload_test.go b/internal/pkg/template/workload_test.go index a9d57c68713..65841deb5af 100644 --- a/internal/pkg/template/workload_test.go +++ b/internal/pkg/template/workload_test.go @@ -7,6 +7,7 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/stretchr/testify/require" ) @@ -332,3 +333,89 @@ func TestWorkload_HealthCheckProtocol(t *testing.T) { }) } } + +func TestEnvControllerParameters(t *testing.T) { + tests := map[string]struct { + opts WorkloadOpts + expected []string + }{ + "LBWS": { + opts: WorkloadOpts{ + WorkloadType: "Load Balanced Web Service", + }, + expected: []string{"Aliases,"}, + }, + "LBWS with ALB": { + opts: WorkloadOpts{ + WorkloadType: "Load Balanced Web Service", + ALBEnabled: true, + }, + expected: []string{"ALBWorkloads,", "Aliases,"}, + }, + "LBWS with ALB and private placement": { + opts: WorkloadOpts{ + WorkloadType: "Load Balanced Web Service", + ALBEnabled: true, + Network: NetworkOpts{ + SubnetsType: PrivateSubnetsPlacement, + }, + }, + expected: []string{"ALBWorkloads,", "Aliases,", "NATWorkloads,"}, + }, + "LBWS with ALB, private placement, and storage": { + opts: WorkloadOpts{ + WorkloadType: "Load Balanced Web Service", + ALBEnabled: true, + Network: NetworkOpts{ + SubnetsType: PrivateSubnetsPlacement, + }, + Storage: &StorageOpts{ + ManagedVolumeInfo: &ManagedVolumeCreationInfo{ + Name: aws.String("hi"), + }, + }, + }, + expected: []string{"ALBWorkloads,", "Aliases,", "NATWorkloads,", "EFSWorkloads,"}, + }, + "Backend": { + opts: WorkloadOpts{ + WorkloadType: "Backend Service", + }, + expected: []string{}, + }, + "Backend with ALB": { + opts: WorkloadOpts{ + WorkloadType: "Backend Service", + ALBEnabled: true, + }, + expected: []string{"InternalALBWorkloads,"}, + }, + "RDWS": { + opts: WorkloadOpts{ + WorkloadType: "Request-Driven Web Service", + }, + expected: []string{}, + }, + "private RDWS": { + opts: WorkloadOpts{ + WorkloadType: "Request-Driven Web Service", + Private: true, + }, + expected: []string{"AppRunnerPrivateWorkloads,"}, + }, + "private RDWS with imported VPC Endpoint": { + opts: WorkloadOpts{ + WorkloadType: "Request-Driven Web Service", + Private: true, + AppRunnerVPCEndpoint: aws.String("vpce-1234"), + }, + expected: []string{}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expected, envControllerParameters(tc.opts)) + }) + } +} From 8af845252cfff05c66e772ba65ad97c70e72ea20 Mon Sep 17 00:00:00 2001 From: Daniel Randall <10566468+dannyrandall@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:15:13 -0700 Subject: [PATCH 6/7] update aws sdk --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4b524d69f90..12a00fee1e9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/AlecAivazis/survey/v2 v2.3.2 - github.com/aws/aws-sdk-go v1.44.121 + github.com/aws/aws-sdk-go v1.44.127 github.com/briandowns/spinner v1.19.0 github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.13.0 diff --git a/go.sum b/go.sum index 023600756f3..6a6fc9fb01b 100644 --- a/go.sum +++ b/go.sum @@ -180,8 +180,8 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.44.121 h1:ahBRUqUp4qLyGmSM5KKn+TVpZkRmtuLxTWw+6Hq/ebs= -github.com/aws/aws-sdk-go v1.44.121/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0= +github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From a162d8bfc0be0ee87f0716f17df1b159432aac58 Mon Sep 17 00:00:00 2001 From: Daniel Randall <10566468+dannyrandall@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:22:59 -0700 Subject: [PATCH 7/7] use correct vpce endpoint & iam principles --- .../testdata/environments/template-with-basic-manifest.yml | 3 +-- .../environments/template-with-custom-security-group.yml | 3 +-- .../environments/template-with-default-access-log-config.yml | 3 +-- .../environments/template-with-defaultvpc-flowlogs.yml | 3 +-- .../template-with-imported-certs-observability.yml | 3 +-- ...h-imported-certs-sslpolicy-custom-empty-security-group.yml | 3 +-- .../environments/template-with-importedvpc-flowlogs.yml | 3 +-- .../stack/testdata/workloads/rdws-prod.stack.yml | 4 ---- .../stack/testdata/workloads/rdws-test.stack.yml | 4 ---- .../templates/environment/partials/ar-vpc-connector.yml | 3 +-- .../template/templates/workloads/partials/cf/accessrole.yml | 2 -- .../template/templates/workloads/partials/cf/instancerole.yml | 2 -- 12 files changed, 8 insertions(+), 28 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml index 042c2106f4b..439065144e0 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml @@ -1029,8 +1029,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml index 71ac109bfa6..f1d80e3d8b6 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml @@ -941,8 +941,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml index a2a347201fc..49863decaa6 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml @@ -1125,8 +1125,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index 4391a540d5b..3ed21b7a7a4 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -1077,8 +1077,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml index a05b0fe8212..28fb32bfa1d 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml @@ -1021,8 +1021,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml index f1fa420eea6..fa5bf11e015 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-sslpolicy-custom-empty-security-group.yml @@ -918,8 +918,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index 98bff09f4c9..56f75d92f40 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -907,8 +907,7 @@ Resources: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml index 3703bc203ff..b7fe3d3e474 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-prod.stack.yml @@ -71,8 +71,6 @@ Resources: Principal: Service: - build.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: @@ -90,8 +88,6 @@ Resources: Principal: Service: - tasks.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles' diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml index 4b586d662e5..b8d3a485bab 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/rdws-test.stack.yml @@ -71,8 +71,6 @@ Resources: Principal: Service: - build.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: @@ -90,8 +88,6 @@ Resources: Principal: Service: - tasks.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles' diff --git a/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml b/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml index 19c3b0864f7..1619af2cb1e 100644 --- a/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml +++ b/internal/pkg/template/templates/environment/partials/ar-vpc-connector.yml @@ -33,8 +33,7 @@ AppRunnerVpcEndpoint: VpcId: !Ref VPC SecurityGroupIds: - !Ref AppRunnerVpcEndpointSecurityGroup - # ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner' - ServiceName: !Sub "com.amazonaws.${AWS::Region}.apprunner.requests.gamma" # TODO switch to non-beta on release + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.apprunner.requests' SubnetIds: {{- range $ind, $cidr := .VPCConfig.Managed.PrivateSubnetCIDRs}} - !Ref PrivateSubnet{{inc $ind}} diff --git a/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml b/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml index d1421489678..2847045480f 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/accessrole.yml @@ -14,8 +14,6 @@ AccessRole: Principal: Service: - build.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.cells.build.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.cells.build.bullet.aws.internal' # TODO remove on release Action: sts:AssumeRole ManagedPolicyArns: diff --git a/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml b/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml index d2d5ab28711..b530946fcca 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/instancerole.yml @@ -21,8 +21,6 @@ InstanceRole: Principal: Service: - tasks.apprunner.amazonaws.com - - !Sub '${AWS::Region}.beta.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release - - !Sub '${AWS::Region}.gamma.credentialvendingserviceproxy.bullet.aws.internal' # TODO remove on release Action: 'sts:AssumeRole' Policies: - PolicyName: 'DenyIAMExceptTaggedRoles'