diff --git a/api/types/provisioning.go b/api/types/provisioning.go index 3e98b5125d2..e3c35dabcee 100644 --- a/api/types/provisioning.go +++ b/api/types/provisioning.go @@ -98,6 +98,8 @@ type ProvisionToken interface { SetRoles(SystemRoles) // GetAllowRules returns the list of allow rules GetAllowRules() []*TokenRule + // SetAllowRules sets the allow rules + SetAllowRules([]*TokenRule) // GetAWSIIDTTL returns the TTL of EC2 IIDs GetAWSIIDTTL() Duration // GetJoinMethod returns joining method that must be used with this token. @@ -327,6 +329,11 @@ func (p *ProvisionTokenV2) GetAllowRules() []*TokenRule { return p.Spec.Allow } +// SetAllowRules sets the allow rules. +func (p *ProvisionTokenV2) SetAllowRules(rules []*TokenRule) { + p.Spec.Allow = rules +} + // GetAWSIIDTTL returns the TTL of EC2 IIDs func (p *ProvisionTokenV2) GetAWSIIDTTL() Duration { return p.Spec.AWSIIDTTL diff --git a/lib/integrations/awsoidc/clients.go b/lib/integrations/awsoidc/clients.go index eb4535ef84c..73b6de47b29 100644 --- a/lib/integrations/awsoidc/clients.go +++ b/lib/integrations/awsoidc/clients.go @@ -111,6 +111,16 @@ func newECSClient(ctx context.Context, req *AWSClientRequest) (*ecs.Client, erro return ecs.NewFromConfig(*cfg), nil } +// newSTSClient creates an [sts.Client] using the provided Token, RoleARN and Region. +func newSTSClient(ctx context.Context, req *AWSClientRequest) (*sts.Client, error) { + cfg, err := newAWSConfig(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + + return sts.NewFromConfig(*cfg), nil +} + // IdentityToken is an implementation of [stscreds.IdentityTokenRetriever] for returning a static token. type IdentityToken string diff --git a/lib/integrations/awsoidc/deployservice.go b/lib/integrations/awsoidc/deployservice.go index a01b23cb129..15dae3bc041 100644 --- a/lib/integrations/awsoidc/deployservice.go +++ b/lib/integrations/awsoidc/deployservice.go @@ -22,8 +22,10 @@ import ( "strings" "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ecs" ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/gravitational/trace" "golang.org/x/exp/slices" @@ -91,6 +93,10 @@ type DeployServiceRequest struct { // Region is the AWS Region Region string + // AccountID is the AWS Account ID. + // Optional. sts.GetCallerIdentity is used if the value is not provided. + AccountID string + // SubnetIDs are the subnets associated with the service. SubnetIDs []string @@ -269,11 +275,54 @@ type DeployServiceClient interface { // RegisterTaskDefinition registers a new task definition from the supplied family and containerDefinitions. // https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ecs@v1.27.1#Client.RegisterTaskDefinition RegisterTaskDefinition(ctx context.Context, params *ecs.RegisterTaskDefinitionInput, optFns ...func(*ecs.Options)) (*ecs.RegisterTaskDefinitionOutput, error) + + // TokenService are the required methods to manage the IAM Join Token. + // When the deployed service connects to the cluster, it will use the IAM Join method. + // Before deploying the service, it must ensure that the token exists and has the appropriate token rul. + TokenService + + // GetCallerIdentity returns details about the IAM user or role whose credentials are used to call the operation. + GetCallerIdentity(ctx context.Context, params *sts.GetCallerIdentityInput, optFns ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error) +} + +type defaultDeployServiceClient struct { + *ecs.Client + stsClient *sts.Client + tokenServiceClient TokenService +} + +// GetToken returns a provision token by name. +func (d *defaultDeployServiceClient) GetToken(ctx context.Context, name string) (types.ProvisionToken, error) { + return d.tokenServiceClient.GetToken(ctx, name) +} + +// UpsertToken creates or updates a provision token. +func (d *defaultDeployServiceClient) UpsertToken(ctx context.Context, token types.ProvisionToken) error { + return d.tokenServiceClient.UpsertToken(ctx, token) +} + +// GetCallerIdentity returns details about the IAM user or role whose credentials are used to call the operation. +func (d defaultDeployServiceClient) GetCallerIdentity(ctx context.Context, params *sts.GetCallerIdentityInput, optFns ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error) { + return d.stsClient.GetCallerIdentity(ctx, params, optFns...) } // NewDeployServiceClient creates a new DeployServiceClient using a AWSClientRequest. -func NewDeployServiceClient(ctx context.Context, clientReq *AWSClientRequest) (DeployServiceClient, error) { - return newECSClient(ctx, clientReq) +func NewDeployServiceClient(ctx context.Context, clientReq *AWSClientRequest, tokenServiceClient TokenService) (DeployServiceClient, error) { + ecsClient, err := newECSClient(ctx, clientReq) + if err != nil { + return nil, trace.Wrap(err) + } + + stsClient, err := newSTSClient(ctx, clientReq) + if err != nil { + return nil, trace.Wrap(err) + } + + return &defaultDeployServiceClient{ + Client: ecsClient, + stsClient: stsClient, + tokenServiceClient: tokenServiceClient, + }, nil } // DeployService calls Amazon ECS APIs to deploy a Teleport Service. @@ -312,6 +361,26 @@ func DeployService(ctx context.Context, clt DeployServiceClient, req DeployServi return nil, trace.Wrap(err) } + if req.AccountID == "" { + callerIdentity, err := clt.GetCallerIdentity(ctx, nil) + if err != nil { + return nil, trace.Wrap(err) + } + + req.AccountID = aws.ToString(callerIdentity.Account) + } + + upsertTokenReq := upsertIAMJoinTokenRequest{ + tokenName: *req.TeleportIAMTokenName, + accountID: req.AccountID, + region: req.Region, + iamRole: req.TaskRoleARN, + deploymentMode: req.DeploymentMode, + } + if err := upsertIAMJoinToken(ctx, upsertTokenReq, clt); err != nil { + return nil, trace.Wrap(err) + } + teleportConfigString, err := generateTeleportConfigString(req) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/integrations/awsoidc/deployservice_iam_jointoken.go b/lib/integrations/awsoidc/deployservice_iam_jointoken.go new file mode 100644 index 00000000000..612e38e7dfc --- /dev/null +++ b/lib/integrations/awsoidc/deployservice_iam_jointoken.go @@ -0,0 +1,188 @@ +/* +Copyright 2023 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package awsoidc + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/gravitational/trace" + "golang.org/x/exp/slices" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils" + awsutils "github.com/gravitational/teleport/api/utils/aws" +) + +const ( + // awsSTSShortName is the service name of the AWS STS + // Used to build the ARN matcher in the IAM Join Token. + awsSTSShortName = "sts" +) + +// TokenService defines the required methods to upsert the Provision Token used by the Deploy Service. +type TokenService interface { + // GetToken returns a provision token by name. + GetToken(ctx context.Context, name string) (types.ProvisionToken, error) + + // UpsertToken creates or updates a provision token. + UpsertToken(ctx context.Context, token types.ProvisionToken) error +} + +// upsertIAMJoinTokenRequest is the request to create or update a Provision Token that has a rule which allows AWS Identities to join the cluster. +// Allowed AWS Identities are the ones described +// It gives access for those identities to join as system roles. +type upsertIAMJoinTokenRequest struct { + // tokenName is the Token's name to create or update. + tokenName string + + // accountID is the allowed AWS Account ID. + accountID string + + // region is the allowed AWS Region. + region string + + // iamRole is the allowed IAM Role. + iamRole string + + // deploymentMode is the service configuration that is going to be deployed + deploymentMode string + + // awsARN is the IAM Role's ARN. + // This value is calculated using the AWS account, region and IAM role. + awsARN string +} + +// CheckAndSetDefaults verifies the required fields are present. +func (u *upsertIAMJoinTokenRequest) CheckAndSetDefaults() error { + if u.tokenName == "" { + return trace.BadParameter("token name is required") + } + + if u.accountID == "" { + return trace.BadParameter("accound id is required") + } + + if u.region == "" { + return trace.BadParameter("region is required") + } + + if u.iamRole == "" { + return trace.BadParameter("iam role is required") + } + + if !slices.Contains(DeploymentModes, u.deploymentMode) { + return trace.BadParameter("invalid deployment mode, please use one of the following: %v", DeploymentModes) + } + + partition := awsutils.GetPartitionFromRegion(u.region) + + // Example ARN: + // arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143717490080920 + u.awsARN = arn.ARN{ + Partition: partition, + Service: awsSTSShortName, + AccountID: u.accountID, + Resource: fmt.Sprintf("assumed-role/%s/*", u.iamRole), + }.String() + + return nil +} + +// upsertIAMJoinToken creates or updates a Provision Token with a rule that allows Teleport Services with given AWS Identities to join the cluster. +// The allowed Identities are the ones provided in the request (Account, Region and AWSRole). +// It gives access for those identities to join as system roles. +func upsertIAMJoinToken(ctx context.Context, req upsertIAMJoinTokenRequest, clt TokenService) error { + if err := req.CheckAndSetDefaults(); err != nil { + return trace.Wrap(err) + } + + rule := &types.TokenRule{ + AWSAccount: req.accountID, + AWSARN: req.awsARN, + } + + token, err := clt.GetToken(ctx, req.tokenName) + switch { + case err != nil && !trace.IsNotFound(err): + return trace.Wrap(err) + + case trace.IsNotFound(err): + token = &types.ProvisionTokenV2{ + Metadata: types.Metadata{ + Name: req.tokenName, + }, + Spec: types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodIAM, + }, + } + fallthrough + default: + existingToken, ok := token.(*types.ProvisionTokenV2) + if !ok { + return trace.BadParameter("expected token to be of type ProvisionTokenV2, got %T", token) + } + + token, err = updateTokenIAMJoin(ctx, req, rule, existingToken) + if trace.IsAlreadyExists(err) { + // Early return when the required rule already exists. + return nil + } + if err != nil { + return trace.Wrap(err) + } + } + + if err := clt.UpsertToken(ctx, token); err != nil { + return trace.Wrap(err) + } + + return nil +} +func updateTokenIAMJoin(ctx context.Context, req upsertIAMJoinTokenRequest, rule *types.TokenRule, existingToken *types.ProvisionTokenV2) (types.ProvisionToken, error) { + if existingToken.GetJoinMethod() != types.JoinMethodIAM { + return nil, trace.BadParameter("Token %q already exists but has the wrong join method %q. "+ + "Please remove it before continuing.", + req.tokenName, existingToken.GetJoinMethod(), + ) + } + + allRoles := existingToken.GetRoles() + + switch req.deploymentMode { + case DatabaseServiceDeploymentMode: + allRoles = append(allRoles, types.RoleDatabase) + + default: + return nil, trace.BadParameter("invalid deployment mode %q, supported modes: %v", req.deploymentMode, DeploymentModes) + } + + uniqueRoles := utils.Deduplicate(allRoles) + existingToken.SetRoles(uniqueRoles) + + for _, rule := range existingToken.GetAllowRules() { + if rule.AWSAccount == req.accountID && rule.AWSARN == req.awsARN { + + return nil, trace.AlreadyExists("rule already exists") + } + } + + existingToken.SetAllowRules(append(existingToken.GetAllowRules(), rule)) + + return existingToken, nil +} diff --git a/lib/integrations/awsoidc/deployservice_iam_jointoken_test.go b/lib/integrations/awsoidc/deployservice_iam_jointoken_test.go new file mode 100644 index 00000000000..7d96476b11d --- /dev/null +++ b/lib/integrations/awsoidc/deployservice_iam_jointoken_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2023 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package awsoidc + +import ( + "context" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +type mockGetUpsertToken struct { + token types.ProvisionToken +} + +// GetToken returns a provision token by name. +func (m *mockGetUpsertToken) GetToken(ctx context.Context, name string) (types.ProvisionToken, error) { + if m.token != nil && name == m.token.GetName() { + return m.token, nil + } + + return nil, trace.NotFound("token not found") +} + +// UpsertToken creates or updates a provision token. +func (m *mockGetUpsertToken) UpsertToken(ctx context.Context, token types.ProvisionToken) error { + m.token = token + + return nil +} + +func TestUpsertIAMJoinToken(t *testing.T) { + ctx := context.Background() + + t.Run("when token doesnt exist, it is created", func(t *testing.T) { + m := &mockGetUpsertToken{} + + err := upsertIAMJoinToken(ctx, upsertIAMJoinTokenRequest{ + tokenName: "t", + accountID: "123456789012", + region: "us-east-1", + iamRole: "myrole", + deploymentMode: DatabaseServiceDeploymentMode, + }, m) + require.NoError(t, err) + + iamToken, err := m.GetToken(ctx, "t") + require.NoError(t, err) + + require.Equal(t, "t", iamToken.GetName()) + require.Contains(t, iamToken.GetRoles(), types.RoleDatabase) + require.Len(t, iamToken.GetAllowRules(), 1) + require.Equal(t, iamToken.GetAllowRules()[0], &types.TokenRule{ + AWSAccount: "123456789012", + AWSARN: "arn:aws:sts::123456789012:assumed-role/myrole/*", + }) + }) + + t.Run("when token exist but is missing the required allow rule and system role, it is updated", func(t *testing.T) { + m := &mockGetUpsertToken{ + token: &types.ProvisionTokenV2{ + Metadata: types.Metadata{ + Name: "t", + }, + Spec: types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodIAM, + Roles: []types.SystemRole{}, + Allow: []*types.TokenRule{}, + }, + }, + } + + err := upsertIAMJoinToken(ctx, upsertIAMJoinTokenRequest{ + tokenName: "t", + accountID: "123456789012", + region: "us-east-1", + iamRole: "myrole", + deploymentMode: DatabaseServiceDeploymentMode, + }, m) + require.NoError(t, err) + + iamToken, err := m.GetToken(ctx, "t") + require.NoError(t, err) + + require.Equal(t, "t", iamToken.GetName()) + require.Len(t, iamToken.GetAllowRules(), 1) + require.Contains(t, iamToken.GetRoles(), types.RoleDatabase) + require.Equal(t, iamToken.GetAllowRules()[0], &types.TokenRule{ + AWSAccount: "123456789012", + AWSARN: "arn:aws:sts::123456789012:assumed-role/myrole/*", + }) + }) + + t.Run("when token exist but has an invalid join method, it returns an error", func(t *testing.T) { + m := &mockGetUpsertToken{ + token: &types.ProvisionTokenV2{ + Metadata: types.Metadata{ + Name: "t", + }, + Spec: types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodEC2, + Roles: []types.SystemRole{}, + Allow: []*types.TokenRule{}, + }, + }, + } + + err := upsertIAMJoinToken(ctx, upsertIAMJoinTokenRequest{ + tokenName: "t", + accountID: "123456789012", + region: "us-east-1", + iamRole: "myrole", + deploymentMode: DatabaseServiceDeploymentMode, + }, m) + require.ErrorContains(t, err, `Token "t" already exists but has the wrong join method "ec2". Please remove it before continuing.`) + }) + + t.Run("when deployment method is invalid, it returns an error", func(t *testing.T) { + m := &mockGetUpsertToken{} + + err := upsertIAMJoinToken(ctx, upsertIAMJoinTokenRequest{ + tokenName: "t", + accountID: "123456789012", + region: "us-east-1", + iamRole: "myrole", + deploymentMode: "invalid-deploy-method", + }, m) + require.ErrorContains(t, err, "invalid deployment mode, please use one of the following: [database-service]") + }) +} diff --git a/lib/integrations/awsoidc/deployservice_vcr_test.go b/lib/integrations/awsoidc/deployservice_vcr_test.go index 60621290c6e..2a470bd778a 100644 --- a/lib/integrations/awsoidc/deployservice_vcr_test.go +++ b/lib/integrations/awsoidc/deployservice_vcr_test.go @@ -42,8 +42,10 @@ func TestDeployDBService(t *testing.T) { awsOIDCToken := "x.y.z" awsRegion := "us-east-1" - awsOIDCRoleARN := "arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider" + awsAccountID := "278576220453" + awsOIDCRoleARN := "arn:aws:iam::" + awsAccountID + ":role/MarcoTestRoleOIDCProvider" integrationName := "teleportdev" + taskRole := "MarcoEC2Role" removeKeysRegex, err := regexp.Compile(`(?s)().*().*().*().*().*()`) require.NoError(t, err) @@ -75,7 +77,8 @@ func TestDeployDBService(t *testing.T) { deployServiceReqFunc := func(clusterName string) DeployServiceRequest { return DeployServiceRequest{ - Region: awsRegion, + Region: awsRegion, + AccountID: awsAccountID, SubnetIDs: []string{ "subnet-0b7ab67161173748b", "subnet-0dda93c8621eb2e99", @@ -84,9 +87,9 @@ func TestDeployDBService(t *testing.T) { "subnet-0ef025345dd791986", "subnet-099632749366c2c56", }, - TaskRoleARN: "MarcoEC2Role", + TaskRoleARN: taskRole, TeleportClusterName: clusterName, - IntegrationName: "teleportdev", + IntegrationName: integrationName, DeploymentMode: DatabaseServiceDeploymentMode, ProxyServerHostPort: "marcodinis.teleportdemo.net:443", DatabaseResourceMatcherLabels: types.Labels{ @@ -106,15 +109,36 @@ func TestDeployDBService(t *testing.T) { return r } + rule := &types.TokenRule{ + AWSAccount: "123456789012", + AWSRegions: []string{awsRegion}, + AWSRole: taskRole, + } + + iamJoinToken := &types.ProvisionTokenV2{ + Metadata: types.Metadata{ + Name: defaultTeleportIAMTokenName, + }, + Spec: types.ProvisionTokenSpecV2{ + JoinMethod: types.JoinMethodIAM, + Roles: types.SystemRoles{types.RoleDatabase}, + Allow: []*types.TokenRule{rule}, + }, + } + + tokenStore := mockGetUpsertToken{ + token: iamJoinToken, + } + t.Run("nothing exists in aws account", func(t *testing.T) { r := mustRecordUsing(t, "fixtures/emptyaccount") defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - resp, err := DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + resp, err := DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.NoError(t, err) require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/cluster1002-teleport", resp.ClusterARN) @@ -128,10 +152,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - resp, err := DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + resp, err := DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.NoError(t, err) require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/cluster1002-teleport", resp.ClusterARN) @@ -145,10 +169,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - _, err = DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + _, err = DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.ErrorContains(t, err, "ECS Service is draining, please retry in a couple of minutes") }) @@ -157,10 +181,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - resp, err := DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + resp, err := DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.NoError(t, err) require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/cluster1002-teleport", resp.ClusterARN) @@ -174,10 +198,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - resp, err := DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + resp, err := DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.NoError(t, err) require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/cluster1002-teleport", resp.ClusterARN) @@ -191,10 +215,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - _, err = DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + _, err = DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.ErrorContains(t, err, `ECS Cluster "cluster1002-teleport" already exists but is not managed by Teleport. Add the following tags to allow Teleport to manage this cluster:`) }) @@ -203,10 +227,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - _, err = DeployService(ctx, ecsClient, deployServiceReqFunc("cluster1002")) + _, err = DeployService(ctx, clt, deployServiceReqFunc("cluster1002")) require.ErrorContains(t, err, `ECS Service "cluster1002-teleport-database-service" already exists but is not managed by Teleport. Add the following tags to allow Teleport to manage this service:`) }) @@ -215,10 +239,10 @@ func TestDeployDBService(t *testing.T) { defer r.Stop() awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) - ecsClient, err := newECSClient(ctx, awsClientRecorder) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) require.NoError(t, err) - resp, err := DeployService(ctx, ecsClient, deployServiceReqFunc("tenant-a.teleport.sh")) + resp, err := DeployService(ctx, clt, deployServiceReqFunc("tenant-a.teleport.sh")) require.NoError(t, err) require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/tenant-a_teleport_sh-teleport", resp.ClusterARN) @@ -226,4 +250,23 @@ func TestDeployDBService(t *testing.T) { require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:task-definition/tenant-a_teleport_sh-teleport-database-service:1", resp.TaskDefinitionARN) require.Equal(t, "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/tenant-a_teleport_sh-teleport/services/tenant-a_teleport_sh-teleport-database-service", resp.ServiceDashboardURL) }) + + t.Run("deploying without providing an AccountID", func(t *testing.T) { + r := mustRecordUsing(t, "fixtures/without_account_id") + defer r.Stop() + + awsClientRecorder := awsClientReqFunc(r.GetDefaultClient()) + clt, err := NewDeployServiceClient(ctx, awsClientRecorder, &tokenStore) + require.NoError(t, err) + + deployReq := deployServiceReqFunc("marco-test.teleport.sh") + deployReq.AccountID = "" + resp, err := DeployService(ctx, clt, deployReq) + require.NoError(t, err) + + require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:cluster/marco-test_teleport_sh-teleport", resp.ClusterARN) + require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:service/marco-test_teleport_sh-teleport/marco-test_teleport_sh-teleport-database-service", resp.ServiceARN) + require.Equal(t, "arn:aws:ecs:us-east-1:278576220453:task-definition/marco-test_teleport_sh-teleport-database-service:1", resp.TaskDefinitionARN) + require.Equal(t, "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/marco-test_teleport_sh-teleport/services/marco-test_teleport_sh-teleport-database-service", resp.ServiceDashboardURL) + }) } diff --git a/lib/integrations/awsoidc/fixtures/without_account_id.yaml b/lib/integrations/awsoidc/fixtures/without_account_id.yaml new file mode 100644 index 00000000000..8486a95d28a --- /dev/null +++ b/lib/integrations/awsoidc/fixtures/without_account_id.yaml @@ -0,0 +1,735 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143003997812852" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - 4490a0f5-e4e2-4fc1-abaf-8fe12b15db90 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143003997812852 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143003997812852 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:45Z + + system:proxy + + + 3bdfbb2b-fa78-4408-9bd3-1beed5c0ef5c + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:44 GMT + X-Amzn-Requestid: + - 3bdfbb2b-fa78-4408-9bd3-1beed5c0ef5c + status: 200 OK + code: 200 + duration: 1.156135491s + - id: 1 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 43 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - GetCallerIdentity + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - b6da4111-9009-421d-8512-0cba88a4b753 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + X-Amz-Date: + - 20230630T163645Z + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 470 + uncompressed: false + body: | + + + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143003997812852 + AROAUBXDPZES62GD44VBR:1688143003997812852 + 278576220453 + + + dd49d394-8611-4132-abb5-c3a83bc470ee + + + headers: + Content-Length: + - "470" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:44 GMT + X-Amzn-Requestid: + - dd49d394-8611-4132-abb5-c3a83bc470ee + status: 200 OK + code: 200 + duration: 148.178727ms + - id: 2 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143005305146353" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - 589ceea1-b392-4a4c-9742-d70dc57933b2 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143005305146353 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143005305146353 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:45Z + + system:proxy + + + 41e3b73d-0155-4815-b2c4-dc9b659fa094 + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:44 GMT + X-Amzn-Requestid: + - 41e3b73d-0155-4815-b2c4-dc9b659fa094 + status: 200 OK + code: 200 + duration: 135.889596ms + - id: 3 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 1758 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: '{"containerDefinitions":[{"command":["start","--config-string","dmVyc2lvbjogdjMKdGVsZXBvcnQ6CiAgZGF0YV9kaXI6IC92YXIvbGliL3RlbGVwb3J0CiAgam9pbl9wYXJhbXM6CiAgICB0b2tlbl9uYW1lOiBkaXNjb3Zlci1hd3Mtb2lkYy1pYW0tdG9rZW4KICAgIG1ldGhvZDogaWFtCiAgcHJveHlfc2VydmVyOiBtYXJjb2RpbmlzLnRlbGVwb3J0ZGVtby5uZXQ6NDQzCiAgbG9nOgogICAgb3V0cHV0OiBzdGRlcnIKICAgIHNldmVyaXR5OiBJTkZPCiAgICBmb3JtYXQ6CiAgICAgIG91dHB1dDogdGV4dAogIGNhX3BpbjogIiIKICBkaWFnX2FkZHI6ICIiCmF1dGhfc2VydmljZToKICBlbmFibGVkOiAibm8iCiAgbGlzdGVuX2FkZHI6IDAuMC4wLjA6MzAyNQogIHByb3h5X2xpc3RlbmVyX21vZGU6IG11bHRpcGxleApzc2hfc2VydmljZToKICBlbmFibGVkOiAibm8iCiAgY29tbWFuZHM6CiAgLSBuYW1lOiBob3N0bmFtZQogICAgY29tbWFuZDogW2hvc3RuYW1lXQogICAgcGVyaW9kOiAxbTBzCnByb3h5X3NlcnZpY2U6CiAgZW5hYmxlZDogIm5vIgogIGh0dHBzX2tleXBhaXJzOiBbXQogIGh0dHBzX2tleXBhaXJzX3JlbG9hZF9pbnRlcnZhbDogMHMKICBhY21lOiB7fQpkYl9zZXJ2aWNlOgogIGVuYWJsZWQ6ICJ5ZXMiCiAgZGF0YWJhc2VzOiBbXQogIHJlc291cmNlczoKICAtIGxhYmVsczoKICAgICAgJyonOiAnKicK"],"entryPoint":["teleport"],"image":"public.ecr.aws/gravitational/teleport-distroless:13.1.5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-region":"us-east-1","awslogs-stream-prefix":"marco-test_teleport_sh-teleport-database-service/marco-test_teleport_sh-teleport-database-service","awslogs-create-group":"true","awslogs-group":"ecs-marco-test_teleport_sh-teleport"}},"name":"teleport-service"}],"cpu":"2048","executionRoleArn":"MarcoEC2Role","family":"marco-test_teleport_sh-teleport-database-service","memory":"4096","networkMode":"awsvpc","requiresCompatibilities":["FARGATE"],"tags":[{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/integration","value":"teleportdev"}],"taskRoleArn":"MarcoEC2Role"}' + form: {} + headers: + Amz-Sdk-Invocation-Id: + - c44070a0-dfef-4ac9-a17d-7630a2d31a7a + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-amz-json-1.1 + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/ecs/1.27.1 + X-Amz-Date: + - 20230630T163645Z + X-Amz-Target: + - AmazonEC2ContainerServiceV20141113.RegisterTaskDefinition + url: https://ecs.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 2709 + uncompressed: false + body: '{"tags":[{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/integration","value":"teleportdev"}],"taskDefinition":{"compatibilities":["EC2","FARGATE"],"containerDefinitions":[{"command":["start","--config-string","dmVyc2lvbjogdjMKdGVsZXBvcnQ6CiAgZGF0YV9kaXI6IC92YXIvbGliL3RlbGVwb3J0CiAgam9pbl9wYXJhbXM6CiAgICB0b2tlbl9uYW1lOiBkaXNjb3Zlci1hd3Mtb2lkYy1pYW0tdG9rZW4KICAgIG1ldGhvZDogaWFtCiAgcHJveHlfc2VydmVyOiBtYXJjb2RpbmlzLnRlbGVwb3J0ZGVtby5uZXQ6NDQzCiAgbG9nOgogICAgb3V0cHV0OiBzdGRlcnIKICAgIHNldmVyaXR5OiBJTkZPCiAgICBmb3JtYXQ6CiAgICAgIG91dHB1dDogdGV4dAogIGNhX3BpbjogIiIKICBkaWFnX2FkZHI6ICIiCmF1dGhfc2VydmljZToKICBlbmFibGVkOiAibm8iCiAgbGlzdGVuX2FkZHI6IDAuMC4wLjA6MzAyNQogIHByb3h5X2xpc3RlbmVyX21vZGU6IG11bHRpcGxleApzc2hfc2VydmljZToKICBlbmFibGVkOiAibm8iCiAgY29tbWFuZHM6CiAgLSBuYW1lOiBob3N0bmFtZQogICAgY29tbWFuZDogW2hvc3RuYW1lXQogICAgcGVyaW9kOiAxbTBzCnByb3h5X3NlcnZpY2U6CiAgZW5hYmxlZDogIm5vIgogIGh0dHBzX2tleXBhaXJzOiBbXQogIGh0dHBzX2tleXBhaXJzX3JlbG9hZF9pbnRlcnZhbDogMHMKICBhY21lOiB7fQpkYl9zZXJ2aWNlOgogIGVuYWJsZWQ6ICJ5ZXMiCiAgZGF0YWJhc2VzOiBbXQogIHJlc291cmNlczoKICAtIGxhYmVsczoKICAgICAgJyonOiAnKicK"],"cpu":0,"entryPoint":["teleport"],"environment":[],"essential":true,"image":"public.ecr.aws/gravitational/teleport-distroless:13.1.5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-create-group":"true","awslogs-group":"ecs-marco-test_teleport_sh-teleport","awslogs-region":"us-east-1","awslogs-stream-prefix":"marco-test_teleport_sh-teleport-database-service/marco-test_teleport_sh-teleport-database-service"}},"mountPoints":[],"name":"teleport-service","portMappings":[],"volumesFrom":[]}],"cpu":"2048","executionRoleArn":"arn:aws:iam::278576220453:role/MarcoEC2Role","family":"marco-test_teleport_sh-teleport-database-service","memory":"4096","networkMode":"awsvpc","placementConstraints":[],"registeredAt":1.68814300616E9,"registeredBy":"arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143005305146353","requiresAttributes":[{"name":"com.amazonaws.ecs.capability.logging-driver.awslogs"},{"name":"ecs.capability.execution-role-awslogs"},{"name":"com.amazonaws.ecs.capability.docker-remote-api.1.19"},{"name":"com.amazonaws.ecs.capability.task-iam-role"},{"name":"com.amazonaws.ecs.capability.docker-remote-api.1.18"},{"name":"ecs.capability.task-eni"},{"name":"com.amazonaws.ecs.capability.docker-remote-api.1.29"}],"requiresCompatibilities":["FARGATE"],"revision":1,"status":"ACTIVE","taskDefinitionArn":"arn:aws:ecs:us-east-1:278576220453:task-definition/marco-test_teleport_sh-teleport-database-service:1","taskRoleArn":"arn:aws:iam::278576220453:role/MarcoEC2Role","volumes":[]}}' + headers: + Content-Length: + - "2709" + Content-Type: + - application/x-amz-json-1.1 + Date: + - Fri, 30 Jun 2023 16:36:46 GMT + X-Amzn-Requestid: + - 4ccc07d8-eec0-4699-90bd-54a26dca3c85 + status: 200 OK + code: 200 + duration: 842.168015ms + - id: 4 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143006284945637" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - eb3fb8bc-4d94-4ef1-8a66-97b43cfee904 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143006284945637 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143006284945637 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:46Z + + system:proxy + + + 3c987e7b-846f-418b-8d68-dc2a3832d79d + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:45 GMT + X-Amzn-Requestid: + - 3c987e7b-846f-418b-8d68-dc2a3832d79d + status: 200 OK + code: 200 + duration: 150.042341ms + - id: 5 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 67 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: '{"clusters":["marco-test_teleport_sh-teleport"],"include":["TAGS"]}' + form: {} + headers: + Amz-Sdk-Invocation-Id: + - ef0bc944-68cd-4118-b1c0-abd607cde639 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-amz-json-1.1 + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/ecs/1.27.1 + X-Amz-Date: + - 20230630T163646Z + X-Amz-Target: + - AmazonEC2ContainerServiceV20141113.DescribeClusters + url: https://ecs.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 132 + uncompressed: false + body: '{"clusters":[],"failures":[{"arn":"arn:aws:ecs:us-east-1:278576220453:cluster/marco-test_teleport_sh-teleport","reason":"MISSING"}]}' + headers: + Content-Length: + - "132" + Content-Type: + - application/x-amz-json-1.1 + Date: + - Fri, 30 Jun 2023 16:36:46 GMT + X-Amzn-Requestid: + - dfc8708c-a3ea-44b6-a669-1b7a20e7ac07 + status: 200 OK + code: 200 + duration: 156.835699ms + - id: 6 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143006593327122" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - b4d0bb2f-2789-45ac-8a2d-ad140f35edfc + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143006593327122 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143006593327122 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:46Z + + system:proxy + + + 671fcccd-faf5-4266-be46-2ae232425426 + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:45 GMT + X-Amzn-Requestid: + - 671fcccd-faf5-4266-be46-2ae232425426 + status: 200 OK + code: 200 + duration: 149.356738ms + - id: 7 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 271 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: '{"capacityProviders":["FARGATE"],"clusterName":"marco-test_teleport_sh-teleport","tags":[{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/integration","value":"teleportdev"}]}' + form: {} + headers: + Amz-Sdk-Invocation-Id: + - 1e0c7868-cb53-4696-826a-2a6b256f8e7f + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-amz-json-1.1 + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/ecs/1.27.1 + X-Amz-Date: + - 20230630T163646Z + X-Amz-Target: + - AmazonEC2ContainerServiceV20141113.CreateCluster + url: https://ecs.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 628 + uncompressed: false + body: '{"cluster":{"activeServicesCount":0,"capacityProviders":["FARGATE"],"clusterArn":"arn:aws:ecs:us-east-1:278576220453:cluster/marco-test_teleport_sh-teleport","clusterName":"marco-test_teleport_sh-teleport","defaultCapacityProviderStrategy":[],"pendingTasksCount":0,"registeredContainerInstancesCount":0,"runningTasksCount":0,"settings":[{"name":"containerInsights","value":"disabled"}],"statistics":[],"status":"ACTIVE","tags":[{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/integration","value":"teleportdev"}]},"clusterCount":0}' + headers: + Content-Length: + - "628" + Content-Type: + - application/x-amz-json-1.1 + Date: + - Fri, 30 Jun 2023 16:36:47 GMT + X-Amzn-Requestid: + - 90e567e8-4be4-43c0-9c5f-68ae95c099c9 + status: 200 OK + code: 200 + duration: 351.106592ms + - id: 8 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143007101947892" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - 02f62841-1540-49a6-b51e-d373c49d8253 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143007101947892 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143007101947892 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:47Z + + system:proxy + + + a4525bba-f394-44ff-9a6c-f53bb561030a + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:46 GMT + X-Amzn-Requestid: + - a4525bba-f394-44ff-9a6c-f53bb561030a + status: 200 OK + code: 200 + duration: 132.303974ms + - id: 9 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 128 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: '{"cluster":"marco-test_teleport_sh-teleport","include":["TAGS"],"services":["marco-test_teleport_sh-teleport-database-service"]}' + form: {} + headers: + Amz-Sdk-Invocation-Id: + - e74d5b55-027a-44f5-8cc8-f5030cf15f9c + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-amz-json-1.1 + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/ecs/1.27.1 + X-Amz-Date: + - 20230630T163647Z + X-Amz-Target: + - AmazonEC2ContainerServiceV20141113.DescribeServices + url: https://ecs.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 149 + uncompressed: false + body: '{"failures":[{"arn":"arn:aws:ecs:us-east-1:278576220453:service/marco-test_teleport_sh-teleport-database-service","reason":"MISSING"}],"services":[]}' + headers: + Content-Length: + - "149" + Content-Type: + - application/x-amz-json-1.1 + Date: + - Fri, 30 Jun 2023 16:36:47 GMT + X-Amzn-Requestid: + - 199a7aa3-5382-4540-819c-878b75ed4ea7 + status: 200 OK + code: 200 + duration: 181.862256ms + - id: 10 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 841 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: "" + form: + Action: + - AssumeRoleWithWebIdentity + RoleArn: + - arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider + RoleSessionName: + - "1688143007417578644" + Version: + - "2011-06-15" + headers: + Amz-Sdk-Invocation-Id: + - 1e040ace-82a4-4df9-b80a-b5296ab6ed86 + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/sts/1.19.2 + url: https://sts.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 1930 + uncompressed: false + body: | + + + discover.teleport + + AROAUBXDPZES62GD44VBR:1688143007417578644 + arn:aws:sts::278576220453:assumed-role/MarcoTestRoleOIDCProvider/1688143007417578644 + + arn:aws:iam::278576220453:oidc-provider/marcodinis.teleportdemo.net + + xxx + 2023-06-30T17:36:47Z + + system:proxy + + + 2cec22c7-83d1-4c7a-bb21-cf81963a07a7 + + + headers: + Content-Length: + - "1930" + Content-Type: + - text/xml + Date: + - Fri, 30 Jun 2023 16:36:46 GMT + X-Amzn-Requestid: + - 2cec22c7-83d1-4c7a-bb21-cf81963a07a7 + status: 200 OK + code: 200 + duration: 157.831407ms + - id: 11 + request: + proto: "" + proto_major: 0 + proto_minor: 0 + content_length: 737 + transfer_encoding: [] + trailer: {} + host: "" + remote_addr: "" + request_uri: "" + body: '{"cluster":"marco-test_teleport_sh-teleport","desiredCount":1,"launchType":"FARGATE","networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"ENABLED","subnets":["subnet-0b7ab67161173748b","subnet-0dda93c8621eb2e99","subnet-034f17b3f7344e375","subnet-04a07d4721a3c96e0","subnet-0ef025345dd791986","subnet-099632749366c2c56"]}},"propagateTags":"SERVICE","serviceName":"marco-test_teleport_sh-teleport-database-service","tags":[{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/integration","value":"teleportdev"}],"taskDefinition":"arn:aws:ecs:us-east-1:278576220453:task-definition/marco-test_teleport_sh-teleport-database-service:1"}' + form: {} + headers: + Amz-Sdk-Invocation-Id: + - 22fb41be-8988-43f5-b171-1fd18c095adf + Amz-Sdk-Request: + - attempt=1; max=3 + Content-Type: + - application/x-amz-json-1.1 + User-Agent: + - aws-sdk-go-v2/1.18.1 os/linux lang/go/1.20.5 md/GOOS/linux md/GOARCH/amd64 api/ecs/1.27.1 + X-Amz-Date: + - 20230630T163647Z + X-Amz-Target: + - AmazonEC2ContainerServiceV20141113.CreateService + url: https://ecs.us-east-1.amazonaws.com/ + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 2455 + uncompressed: false + body: '{"service":{"clusterArn":"arn:aws:ecs:us-east-1:278576220453:cluster/marco-test_teleport_sh-teleport","createdAt":1.688143008251E9,"createdBy":"arn:aws:iam::278576220453:role/MarcoTestRoleOIDCProvider","deploymentConfiguration":{"deploymentCircuitBreaker":{"enable":false,"rollback":false},"maximumPercent":200,"minimumHealthyPercent":100},"deploymentController":{"type":"ECS"},"deployments":[{"createdAt":1.688143008251E9,"desiredCount":1,"failedLaunchTaskCount":0,"failedTasks":0,"id":"ecs-svc/2489331754842210340","launchType":"FARGATE","networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"ENABLED","securityGroups":[],"subnets":["subnet-0b7ab67161173748b","subnet-0dda93c8621eb2e99","subnet-034f17b3f7344e375","subnet-04a07d4721a3c96e0","subnet-0ef025345dd791986","subnet-099632749366c2c56"]}},"pendingCount":0,"platformFamily":"Linux","platformVersion":"1.4.0","replacedTaskCount":0,"rolloutState":"IN_PROGRESS","rolloutStateReason":"ECS deployment ecs-svc/2489331754842210340 in progress.","runningCount":0,"status":"PRIMARY","taskDefinition":"arn:aws:ecs:us-east-1:278576220453:task-definition/marco-test_teleport_sh-teleport-database-service:1","updatedAt":1.688143008251E9}],"desiredCount":1,"enableECSManagedTags":false,"enableExecuteCommand":false,"events":[],"launchType":"FARGATE","loadBalancers":[],"networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"ENABLED","securityGroups":[],"subnets":["subnet-0b7ab67161173748b","subnet-0dda93c8621eb2e99","subnet-034f17b3f7344e375","subnet-04a07d4721a3c96e0","subnet-0ef025345dd791986","subnet-099632749366c2c56"]}},"pendingCount":0,"placementConstraints":[],"placementStrategy":[],"platformFamily":"Linux","platformVersion":"LATEST","propagateTags":"SERVICE","roleArn":"arn:aws:iam::278576220453:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS","runningCount":0,"schedulingStrategy":"REPLICA","serviceArn":"arn:aws:ecs:us-east-1:278576220453:service/marco-test_teleport_sh-teleport/marco-test_teleport_sh-teleport-database-service","serviceName":"marco-test_teleport_sh-teleport-database-service","serviceRegistries":[],"status":"ACTIVE","tags":[{"key":"teleport.dev/cluster","value":"marco-test.teleport.sh"},{"key":"teleport.dev/origin","value":"integration_awsoidc"},{"key":"teleport.dev/integration","value":"teleportdev"}],"taskDefinition":"arn:aws:ecs:us-east-1:278576220453:task-definition/marco-test_teleport_sh-teleport-database-service:1","version":0}}' + headers: + Content-Length: + - "2455" + Content-Type: + - application/x-amz-json-1.1 + Date: + - Fri, 30 Jun 2023 16:36:48 GMT + X-Amzn-Requestid: + - d8ace682-cc96-429f-b3f1-308de7f0cad3 + status: 200 OK + code: 200 + duration: 768.986252ms diff --git a/lib/web/integrations_awsoidc.go b/lib/web/integrations_awsoidc.go index 26a29f43799..6980a58052a 100644 --- a/lib/web/integrations_awsoidc.go +++ b/lib/web/integrations_awsoidc.go @@ -130,7 +130,12 @@ func (h *Handler) awsOIDCDeployService(w http.ResponseWriter, r *http.Request, p return nil, trace.Wrap(err) } - deployDBServiceClient, err := awsoidc.NewDeployServiceClient(ctx, awsClientReq) + clt, err := sctx.GetUserClient(ctx, site) + if err != nil { + return nil, trace.Wrap(err) + } + + deployDBServiceClient, err := awsoidc.NewDeployServiceClient(ctx, awsClientReq, clt) if err != nil { return nil, trace.Wrap(err) } @@ -142,6 +147,7 @@ func (h *Handler) awsOIDCDeployService(w http.ResponseWriter, r *http.Request, p deployServiceResp, err := awsoidc.DeployService(ctx, deployDBServiceClient, awsoidc.DeployServiceRequest{ Region: req.Region, + AccountID: req.AccountID, SubnetIDs: req.SubnetIDs, ClusterName: req.ClusterName, ServiceName: req.ServiceName, diff --git a/lib/web/ui/integration.go b/lib/web/ui/integration.go index 0f42452f5bb..0ae2afdbca2 100644 --- a/lib/web/ui/integration.go +++ b/lib/web/ui/integration.go @@ -132,6 +132,10 @@ type AWSOIDCDeployServiceRequest struct { // Region is the AWS Region for the Service. Region string `json:"region"` + // AccountID is the AWS Account ID. + // Optional. sts.GetCallerIdentity is used if the value is not provided. + AccountID string `json:"accountId"` + // SubnetIDs associated with the Service. // If deploying a Database Service, you should use the SubnetIDs returned by the List Database API call. SubnetIDs []string `json:"subnetIds"`