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"`