From 7ccb7ef891c170d1128909fc829db352bd2b33ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Andr=C3=A9=20Dinis?= Date: Thu, 6 Jul 2023 20:31:44 +0100 Subject: [PATCH] DeployService: auto upsert IAM Join Token (#28537) * DeployService: auto upsert IAM Join Token When using the DeployService, the deployed services (database service only for now) will join the Teleport Cluster using the IAM Join Method. In order to do so, we require an IAM Token that allows the AWS Account ID and ARN of the assumed-role. Instead of asking the user to create it, we do it for them. This PR creates or updates the IAM Join Token. * AccountID is optional when calling DeployService * dry code when upserting the token --- api/types/provisioning.go | 7 + lib/integrations/awsoidc/clients.go | 10 + lib/integrations/awsoidc/deployservice.go | 73 +- .../awsoidc/deployservice_iam_jointoken.go | 188 +++++ .../deployservice_iam_jointoken_test.go | 147 ++++ .../awsoidc/deployservice_vcr_test.go | 83 +- .../awsoidc/fixtures/without_account_id.yaml | 735 ++++++++++++++++++ lib/web/integrations_awsoidc.go | 8 +- lib/web/ui/integration.go | 4 + 9 files changed, 1232 insertions(+), 23 deletions(-) create mode 100644 lib/integrations/awsoidc/deployservice_iam_jointoken.go create mode 100644 lib/integrations/awsoidc/deployservice_iam_jointoken_test.go create mode 100644 lib/integrations/awsoidc/fixtures/without_account_id.yaml 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"`