AWS IAM Roles Anywhere: add support for Web/Console Access (#54594)

* AWS Web/Console Access using IAM Roles Anywhere Integration

This PR adds support for AWS Web/Console Access using the IAM Roles
Anywhere Integration.

When trying to access an AWS App which has an associated:
- integration of AWS RA kind
- Roles Anywhere metadata: profile ARN
- IAM Role

There is a new flow which generates credentials using the:
- trust anchor present in the integration metadata
- profile arn present in the AppServer metadata
- target IAM Role
- and a X.509 certificate generated from the AWS Roles Anywhere CA

Those credentials are then used to generate the signing URL using the
federation service.

* move assume role details to helper func

* remove dead code, fix comment and error message
This commit is contained in:
Marco Dinis
2025-06-23 15:56:46 +01:00
committed by GitHub
parent cfbd9789a5
commit 54d4fd1efd
26 changed files with 601 additions and 133 deletions
@@ -0,0 +1,89 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package integrationv1
import (
"context"
"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/timestamppb"
integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/integrations/awsra"
)
// GenerateAWSRACredentials generates a set of AWS credentials which uses the AWS Roles Anywhere integration.
func (s *Service) GenerateAWSRACredentials(ctx context.Context, req *integrationpb.GenerateAWSRACredentialsRequest) (*integrationpb.GenerateAWSRACredentialsResponse, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
for _, allowedRole := range []types.SystemRole{types.RoleAuth, types.RoleProxy} {
if authz.HasBuiltinRole(*authCtx, string(allowedRole)) {
return s.generateAWSRACredentialsWithoutAuthZ(ctx, req)
}
}
return nil, trace.AccessDenied("credential generation is only available to auth or proxy services")
}
// generateAWSRACredentialsWithoutAuthZ generates a set of AWS credentials which uses the AWS Roles Anywhere integration.
// Bypasses authz and should only be used by other methods that validate AuthZ.
func (s *Service) generateAWSRACredentialsWithoutAuthZ(ctx context.Context, req *integrationpb.GenerateAWSRACredentialsRequest) (*integrationpb.GenerateAWSRACredentialsResponse, error) {
integration, err := s.cache.GetIntegration(ctx, req.GetIntegration())
if err != nil {
return nil, trace.Wrap(err)
}
spec := integration.GetAWSRolesAnywhereIntegrationSpec()
if spec == nil {
return nil, trace.BadParameter("integration %q is not an AWSRA integration", req.Integration)
}
var durationSeconds *int
if req.GetSessionMaxDuration().AsDuration() != 0 {
d := int(req.GetSessionMaxDuration().AsDuration().Seconds())
durationSeconds = &d
}
awsCredentials, err := awsra.GenerateCredentials(ctx, awsra.GenerateCredentialsRequest{
Clock: s.clock,
TrustAnchorARN: spec.TrustAnchorARN,
ProfileARN: req.GetProfileArn(),
RoleARN: req.GetRoleArn(),
SubjectCommonName: req.GetSubjectName(),
DurationSeconds: durationSeconds,
AcceptRoleSessionName: req.GetProfileAcceptsRoleSessionName(),
KeyStoreManager: s.keyStoreManager,
Cache: s.cache,
CreateSession: s.awsRolesAnywhereCreateSessionFn,
})
if err != nil {
return nil, trace.Wrap(err)
}
return &integrationpb.GenerateAWSRACredentialsResponse{
AccessKeyId: awsCredentials.AccessKeyID,
SecretAccessKey: awsCredentials.SecretAccessKey,
SessionToken: awsCredentials.SessionToken,
Expiration: timestamppb.New(awsCredentials.Expiration),
}, nil
}
@@ -0,0 +1,95 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package integrationv1
import (
"testing"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/tlsca"
)
func TestGenerateAWSRACredentials(t *testing.T) {
t.Parallel()
clusterName := "test-cluster"
integrationName := "my-integration"
proxyPublicAddr := "example.com:443"
ca := newCertAuthority(t, types.AWSRACA, clusterName)
ctx, localClient, resourceSvc := initSvc(t, ca, clusterName, proxyPublicAddr)
ig, err := types.NewIntegrationAWSRA(
types.Metadata{Name: integrationName},
&types.AWSRAIntegrationSpecV1{
TrustAnchorARN: "arn:aws:rolesanywhere:eu-west-2:123456789012:trust-anchor/12345678-1234-1234-1234-123456789012",
},
)
require.NoError(t, err)
_, err = localClient.CreateIntegration(ctx, ig)
require.NoError(t, err)
ctx = authorizerForDummyUser(t, ctx, types.RoleSpecV6{
Allow: types.RoleConditions{Rules: []types.Rule{
{Resources: []string{types.KindIntegration}, Verbs: []string{types.VerbUse}},
}},
}, localClient)
t.Run("requesting with an user should return access denied", func(t *testing.T) {
ctx = authorizerForDummyUser(t, ctx, types.RoleSpecV6{
Allow: types.RoleConditions{Rules: []types.Rule{
{Resources: []string{types.KindIntegration}, Verbs: []string{types.VerbUse}},
}},
}, localClient)
_, err := resourceSvc.GenerateAWSRACredentials(ctx, &integrationv1.GenerateAWSRACredentialsRequest{
Integration: integrationName,
RoleArn: "arn:aws:iam::123456789012:role/OpsTeam",
ProfileArn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/12345678-1234-1234-1234-123456789012",
SubjectName: "test",
})
require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %T", err)
})
t.Run("auth and proxy can request credentials", func(t *testing.T) {
for _, allowedRole := range []types.SystemRole{types.RoleAuth, types.RoleProxy} {
ctx = authz.ContextWithUser(ctx, authz.BuiltinRole{
Role: types.RoleInstance,
AdditionalSystemRoles: []types.SystemRole{allowedRole},
Username: string(allowedRole),
Identity: tlsca.Identity{
Username: string(allowedRole),
},
})
_, err := resourceSvc.GenerateAWSRACredentials(ctx, &integrationv1.GenerateAWSRACredentialsRequest{
Integration: integrationName,
RoleArn: "arn:aws:iam::123456789012:role/OpsTeam",
ProfileArn: "arn:aws:rolesanywhere:eu-west-2:123456789012:profile/12345678-1234-1234-1234-123456789012",
ProfileAcceptsRoleSessionName: true,
SubjectName: "test",
})
require.NoError(t, err)
}
})
}
@@ -38,6 +38,7 @@ import (
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/integrations/awsra/createsession"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
)
@@ -69,6 +70,9 @@ type KeyStoreManager interface {
NewSSHKeyPair(ctx context.Context, purpose cryptosuites.KeyPurpose) (*types.SSHKeyPair, error)
// GetSSHSignerFromKeySet selects a usable SSH keypair from the provided key set.
GetSSHSignerFromKeySet(ctx context.Context, keySet types.CAKeySet) (ssh.Signer, error)
// GetTLSCertAndSigner selects a usable TLS keypair from the given CA
// and returns the PEM-encoded TLS certificate and a [crypto.Signer].
GetTLSCertAndSigner(ctx context.Context, ca types.CertAuthority) ([]byte, crypto.Signer, error)
}
// Backend defines the interface for all the backend services that the
@@ -89,6 +93,11 @@ type ServiceConfig struct {
Logger *slog.Logger
Clock clockwork.Clock
Emitter apievents.Emitter
// awsRolesAnywhereCreateSessionFn is a function that creates an AWS Roles Anywhere session.
// This is used to allow mocking in tests, because the real implementation does
// If not set, the default implementation is used.
awsRolesAnywhereCreateSessionFn func(ctx context.Context, req createsession.CreateSessionRequest) (*createsession.CreateSessionResponse, error)
}
// CheckAndSetDefaults checks the ServiceConfig fields and returns an error if
@@ -123,6 +132,10 @@ func (s *ServiceConfig) CheckAndSetDefaults() error {
s.Clock = clockwork.NewRealClock()
}
if s.awsRolesAnywhereCreateSessionFn == nil {
s.awsRolesAnywhereCreateSessionFn = createsession.CreateSession
}
return nil
}
@@ -136,6 +149,8 @@ type Service struct {
logger *slog.Logger
clock clockwork.Clock
emitter apievents.Emitter
awsRolesAnywhereCreateSessionFn func(ctx context.Context, req createsession.CreateSessionRequest) (*createsession.CreateSessionResponse, error)
}
// NewService returns a new Integrations gRPC service.
@@ -152,6 +167,8 @@ func NewService(cfg *ServiceConfig) (*Service, error) {
backend: cfg.Backend,
clock: cfg.Clock,
emitter: cfg.Emitter,
awsRolesAnywhereCreateSessionFn: cfg.awsRolesAnywhereCreateSessionFn,
}, nil
}
@@ -21,8 +21,10 @@ package integrationv1
import (
"cmp"
"context"
"crypto/x509/pkix"
"os"
"testing"
"time"
"github.com/google/uuid"
"github.com/gravitational/trace"
@@ -38,6 +40,7 @@ import (
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/backend/memory"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/integrations/awsra/createsession"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
@@ -888,6 +891,15 @@ func initSvc(t *testing.T, ca types.CertAuthority, clusterName string, proxyPubl
Cache: cache,
KeyStoreManager: keystore.NewSoftwareKeystoreForTests(t),
Emitter: events.NewDiscardEmitter(),
awsRolesAnywhereCreateSessionFn: func(ctx context.Context, req createsession.CreateSessionRequest) (*createsession.CreateSessionResponse, error) {
return &createsession.CreateSessionResponse{
Version: 1,
AccessKeyID: "access-key-id",
SecretAccessKey: "secret-access-key",
SessionToken: "session-token",
Expiration: time.Now().Add(1 * time.Hour).Format(time.RFC3339),
}, nil
},
})
require.NoError(t, err)
@@ -958,6 +970,9 @@ func newCertAuthority(t *testing.T, caType types.CertAuthType, domain string) ty
pub, priv, err := ta.GenerateJWT()
require.NoError(t, err)
key, cert, err := tlsca.GenerateSelfSignedCA(pkix.Name{CommonName: domain}, nil, time.Minute)
require.NoError(t, err)
ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
Type: caType,
ClusterName: domain,
@@ -967,6 +982,10 @@ func newCertAuthority(t *testing.T, caType types.CertAuthType, domain string) ty
PrivateKey: priv,
PrivateKeyType: types.PrivateKeyType_RAW,
}},
TLS: []*types.TLSKeyPair{{
Key: key,
Cert: cert,
}},
},
})
require.NoError(t, err)
+162 -40
View File
@@ -32,8 +32,11 @@ import (
"github.com/aws/smithy-go/tracing/smithyoteltracing"
"github.com/gravitational/trace"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/types/known/durationpb"
integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/integrations/awsra"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/utils/aws/stsutils"
)
@@ -50,17 +53,31 @@ const (
credentialsSourceIntegration
)
// IntegrationGetter is an interface that indicates which APIs are
// required to get an integration.
// Required when using integration credentials.
type IntegrationGetter interface {
// GetIntegration returns the specified integration resource.
GetIntegration(ctx context.Context, name string) (types.Integration, error)
}
// OIDCIntegrationClient is an interface that indicates which APIs are
// required to generate an AWS OIDC integration token.
type OIDCIntegrationClient interface {
// GetIntegration returns the specified integration resource.
GetIntegration(ctx context.Context, name string) (types.Integration, error)
IntegrationGetter
// GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC
// Integration action.
GenerateAWSOIDCToken(ctx context.Context, integrationName string) (string, error)
}
// RolesAnywhereIntegrationClient is an interface that indicates which APIs are
// required to generate a set of AWS credentials using the AWS IAM Roles Anywhere integration.
type RolesAnywhereIntegrationClient interface {
IntegrationGetter
// GenerateAWSRACredentials generates a token to be used to execute an AWS IAM Roles Anywhere integration.
GenerateAWSRACredentials(ctx context.Context, req *integrationpb.GenerateAWSRACredentialsRequest) (*integrationpb.GenerateAWSRACredentialsResponse, error)
}
// STSClient is a subset of the AWS STS API.
type STSClient interface {
stscreds.AssumeRoleAPIClient
@@ -96,10 +113,21 @@ type options struct {
credentialsSource credentialsSource
// integration is the name of the integration to be used to fetch the credentials.
integration string
// integrationGetter provides APIs to get the AWS integration.
// Required if integration credentials are requested.
integrationGetter IntegrationGetter
// oidcIntegrationClient provides APIs to generate AWS OIDC tokens, which
// can then be exchanged for IAM credentials.
// Required if integration credentials are requested.
// Required when integration uses IAM OIDC IdP to obtain credentials.
oidcIntegrationClient OIDCIntegrationClient
// rolesAnywhereIntegrationClient provides APIs to generate AWS credentials.
// Required when integration uses IAM Roles Anywhere service to obtain credentials.
rolesAnywhereIntegrationClient RolesAnywhereIntegrationClient
// rolesAnywhereIntegrationMetadata contains the Roles Anywhere Profile and IAM Role to use.
rolesAnywhereIntegrationMetadata RolesAnywhereMetadata
// customRetryer is a custom retryer to use for the config.
customRetryer func() aws.Retryer
// maxRetries is the maximum number of retries to use for the config.
@@ -129,11 +157,8 @@ func (o *options) checkAndSetDefaults() error {
return trace.BadParameter("integration and ambient credentials cannot be used at the same time")
}
case credentialsSourceIntegration:
if o.integration == "" {
return trace.BadParameter("missing integration name")
}
if o.oidcIntegrationClient == nil {
return trace.BadParameter("missing AWS OIDC integration client")
if err := o.checkIntegrationCredentials(); err != nil {
return trace.Wrap(err)
}
default:
return trace.BadParameter("missing credentials source (ambient or integration)")
@@ -154,6 +179,22 @@ func (o *options) checkAndSetDefaults() error {
return nil
}
func (o *options) checkIntegrationCredentials() error {
if o.integration == "" {
return trace.BadParameter("missing integration name")
}
if o.integrationGetter == nil {
return trace.BadParameter("missing integration getter")
}
if o.oidcIntegrationClient == nil && o.rolesAnywhereIntegrationClient == nil {
return trace.BadParameter("missing AWS integration client")
}
return nil
}
// OptionsFn is an option function for setting additional options
// when getting an AWS config.
type OptionsFn func(*options)
@@ -198,23 +239,58 @@ func WithMaxRetries(maxRetries int) OptionsFn {
}
}
// IntegrationMetadata contains the metadata about the Integration to use
// when using the integration credentials source.
type IntegrationMetadata struct {
// Name of the integration.
// Will be empty when using ambient credentials.
Name string
// RolesAnywhereMetadata contains the metadata about the Roles Anywhere.
// Only set when the Integration is of AWS IAM Roles Anywhere subkind.
RolesAnywhereMetadata RolesAnywhereMetadata
}
// RolesAnywhereMetadata contains the metadata required to use AWS IAM Roles Anywhere
// to generate credentials.
type RolesAnywhereMetadata struct {
// ProfileARN is the ARN of the Roles Anywhere profile.
ProfileARN string
// ProfileAcceptsRoleSessionName indicates whether the profile accepts a role session name.
ProfileAcceptsRoleSessionName bool
// RoleARN is the ARN of the role to assume.
RoleARN string
// IdentityUsername is the username to use when generating the AWS credentials.
// This will be used as the Subject Common Name (CN) in the certificate, and logged in CloudTrail if ProfileAcceptsRoleSessionName is true.
// Should be set to the teleport's username.
IdentityUsername string
// SessionDuration is used to calculate the expiration time for the AWS session.
// Must be lower or equal to the maximum session duration of the role.
// The actual session duration will be the minimum between this value (if not zero) and the Profile's max session duration.
SessionDuration time.Duration
}
// WithCredentialsMaybeIntegration sets the credential source to be
// - ambient if the integration is an empty string
// - integration, otherwise
func WithCredentialsMaybeIntegration(integration string) OptionsFn {
if integration != "" {
return withIntegrationCredentials(integration)
// When using integration, relevant integration metadata must be provided.
func WithCredentialsMaybeIntegration(integrationMetadata IntegrationMetadata) OptionsFn {
if integrationMetadata.Name == "" {
return WithAmbientCredentials()
}
return WithAmbientCredentials()
}
// withIntegrationCredentials configures options with an Integration that must be used to fetch Credentials to assume a role.
// This prevents the usage of AWS environment credentials.
func withIntegrationCredentials(integration string) OptionsFn {
return func(options *options) {
options.credentialsSource = credentialsSourceIntegration
options.integration = integration
options.integration = integrationMetadata.Name
options.rolesAnywhereIntegrationMetadata = integrationMetadata.RolesAnywhereMetadata
}
}
// WithRolesAnywhereIntegrationClient sets the Roles Anywhere integration client.
func WithRolesAnywhereIntegrationClient(c RolesAnywhereIntegrationClient) OptionsFn {
return func(options *options) {
options.rolesAnywhereIntegrationClient = c
options.integrationGetter = c
}
}
@@ -236,6 +312,7 @@ func WithSTSClientProvider(fn STSClientProviderFunc) OptionsFn {
func WithOIDCIntegrationClient(c OIDCIntegrationClient) OptionsFn {
return func(options *options) {
options.oidcIntegrationClient = c
options.integrationGetter = c
}
}
@@ -303,14 +380,17 @@ func getBaseConfig(ctx context.Context, region string, opts *options) (aws.Confi
}
if opts.credentialsSource == credentialsSourceIntegration {
slog.DebugContext(ctx, "Initializing AWS config with OIDC integration credentials",
slog.DebugContext(ctx, "Initializing AWS config with integration credentials",
"region", region,
"integration", opts.integration,
)
provider := &integrationCredentialsProvider{
OIDCIntegrationClient: opts.oidcIntegrationClient,
stsClt: opts.stsClientProvider(cfg),
integrationName: opts.integration,
stsClt: opts.stsClientProvider(cfg),
integrationName: opts.integration,
integrationGetter: opts.integrationGetter,
oidcIntegrationClient: opts.oidcIntegrationClient,
rolesAnywhereIntegrationClient: opts.rolesAnywhereIntegrationClient,
rolesAnywhereProfileMetadata: opts.rolesAnywhereIntegrationMetadata,
}
cc := aws.NewCredentialsCache(provider, awsCredentialsCacheOptions)
_, err := cc.Retrieve(ctx)
@@ -365,33 +445,75 @@ func (t staticIdentityToken) GetIdentityToken() ([]byte, error) {
return []byte(t), nil
}
// integrationCredentialsProvider provides AWS OIDC integration credentials.
// integrationCredentialsProvider provides AWS integration credentials.
type integrationCredentialsProvider struct {
OIDCIntegrationClient
stsClt STSClient
integrationName string
integrationGetter IntegrationGetter
oidcIntegrationClient OIDCIntegrationClient
rolesAnywhereIntegrationClient RolesAnywhereIntegrationClient
rolesAnywhereProfileMetadata RolesAnywhereMetadata
}
// Retrieve provides [aws.Credentials] for an AWS OIDC integration.
// Retrieve provides [aws.Credentials] for an AWS integration.
func (p *integrationCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
integration, err := p.GetIntegration(ctx, p.integrationName)
integration, err := p.integrationGetter.GetIntegration(ctx, p.integrationName)
if err != nil {
return aws.Credentials{}, trace.Wrap(err)
}
spec := integration.GetAWSOIDCIntegrationSpec()
if spec == nil {
return aws.Credentials{}, trace.BadParameter("invalid integration subkind, expected awsoidc, got %s", integration.GetSubKind())
switch integration.GetSubKind() {
case types.IntegrationSubKindAWSOIDC:
if p.oidcIntegrationClient == nil {
return aws.Credentials{}, trace.BadParameter("missing OIDC integration client")
}
spec := integration.GetAWSOIDCIntegrationSpec()
if spec == nil {
return aws.Credentials{}, trace.BadParameter("invalid integration subkind, expected awsoidc, got %s", integration.GetSubKind())
}
token, err := p.oidcIntegrationClient.GenerateAWSOIDCToken(ctx, p.integrationName)
if err != nil {
return aws.Credentials{}, trace.Wrap(err)
}
cred, err := stscreds.NewWebIdentityRoleProvider(
p.stsClt,
spec.RoleARN,
staticIdentityToken(token),
).Retrieve(ctx)
return cred, trace.Wrap(err)
case types.IntegrationSubKindAWSRolesAnywhere:
if p.rolesAnywhereIntegrationClient == nil {
return aws.Credentials{}, trace.BadParameter("missing roles anywhere integration client")
}
resp, err := p.rolesAnywhereIntegrationClient.GenerateAWSRACredentials(ctx, &integrationpb.GenerateAWSRACredentialsRequest{
Integration: p.integrationName,
ProfileArn: p.rolesAnywhereProfileMetadata.ProfileARN,
ProfileAcceptsRoleSessionName: p.rolesAnywhereProfileMetadata.ProfileAcceptsRoleSessionName,
RoleArn: p.rolesAnywhereProfileMetadata.RoleARN,
SubjectName: p.rolesAnywhereProfileMetadata.IdentityUsername,
SessionMaxDuration: durationpb.New(p.rolesAnywhereProfileMetadata.SessionDuration),
})
if err != nil {
return aws.Credentials{}, trace.Wrap(err)
}
return aws.Credentials{
AccessKeyID: resp.AccessKeyId,
SecretAccessKey: resp.SecretAccessKey,
SessionToken: resp.SessionToken,
Expires: resp.Expiration.AsTime(),
Source: awsra.AWSCredentialsSourceRolesAnywhere,
}, nil
default:
return aws.Credentials{}, trace.BadParameter("invalid integration subkind, expected AWS OIDC or AWS Roles Anywhere, got %s", integration.GetSubKind())
}
token, err := p.GenerateAWSOIDCToken(ctx, p.integrationName)
if err != nil {
return aws.Credentials{}, trace.Wrap(err)
}
cred, err := stscreds.NewWebIdentityRoleProvider(
p.stsClt,
spec.RoleARN,
staticIdentityToken(token),
).Retrieve(ctx)
return cred, trace.Wrap(err)
}
// maybeHashRoleSessionName truncates the role session name and adds a hash
+96 -16
View File
@@ -28,7 +28,9 @@ import (
ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
)
@@ -111,11 +113,11 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
return &mockAssumeRoleAPIClient{}
}
t.Run("without an integration client, must return missing credential provider error", func(t *testing.T) {
t.Run("without an integration client, must return missing integration getter error", func(t *testing.T) {
ctx := context.Background()
_, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(dummyIntegration))
_, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}))
require.True(t, trace.IsBadParameter(err), "unexpected error: %v", err)
require.ErrorContains(t, err, "missing AWS OIDC integration client")
require.ErrorContains(t, err, "missing integration getter")
})
t.Run("with an integration client, must return integration fetch error", func(t *testing.T) {
@@ -126,7 +128,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
return nil, trace.NotFound("integration not found")
}
_, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithSTSClientProvider(stsClt),
)
@@ -150,7 +152,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
return azureIntegration, nil
}
_, err = provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithSTSClientProvider(stsClt),
)
@@ -165,7 +167,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
return "", trace.BadParameter("failed to generate OIDC token")
}
_, err = provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithSTSClientProvider(stsClt),
)
@@ -177,7 +179,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
ctx := context.Background()
cfg, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithSTSClientProvider(stsClt),
)
@@ -191,7 +193,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
ctx := context.Background()
cfg, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithAssumeRole("roleA", "abc123"),
WithSTSClientProvider(stsClt),
@@ -206,7 +208,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
t.Run("with an integration credential provider assuming a role, must limit role chain length", func(t *testing.T) {
ctx := context.Background()
_, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(dummyIntegration),
WithCredentialsMaybeIntegration(IntegrationMetadata{Name: dummyIntegration}),
WithOIDCIntegrationClient(&fakeIntegrationClt),
WithAssumeRole("roleA", "abc123"),
WithAssumeRole("roleB", "abc123"),
@@ -221,7 +223,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
ctx := context.Background()
_, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(""),
WithCredentialsMaybeIntegration(IntegrationMetadata{}),
WithOIDCIntegrationClient(&fakeOIDCIntegrationClient{unauth: true}),
)
require.NoError(t, err)
@@ -251,7 +253,7 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
ctx := context.Background()
baseCfg, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(""),
WithCredentialsMaybeIntegration(IntegrationMetadata{}),
WithOIDCIntegrationClient(&fakeOIDCIntegrationClient{unauth: true}),
)
require.NoError(t, err)
@@ -268,6 +270,61 @@ func testGetConfigIntegration(t *testing.T, provider Provider) {
require.Equal(t, "role: roleA, externalID: abc123", creds.AccessKeyID)
require.Equal(t, "fake-session-token", creds.SessionToken)
})
t.Run("with a Roles Anywhere integration", func(t *testing.T) {
ctx := context.Background()
integrationClient := &mockRolesAnywhereClient{
getIntegrationFn: func(context.Context, string) (types.Integration, error) {
awsRAIntegration, err := types.NewIntegrationAWSRA(
types.Metadata{Name: "integration-test"},
&types.AWSRAIntegrationSpecV1{
TrustAnchorARN: "arn:aws:rolesanywhere:eu-west-2:123456789012:trust-anchor/12345678-1234-1234-1234-123456789012",
},
)
require.NoError(t, err)
return awsRAIntegration, nil
},
}
integrationMetadata := IntegrationMetadata{
Name: dummyIntegration,
RolesAnywhereMetadata: RolesAnywhereMetadata{
ProfileARN: "my-profile-arn",
ProfileAcceptsRoleSessionName: true,
RoleARN: "arn:aws:iam::123456789012:role/role",
IdentityUsername: "alice",
},
}
cfg, err := provider.GetConfig(ctx, dummyRegion,
WithCredentialsMaybeIntegration(integrationMetadata),
WithRolesAnywhereIntegrationClient(integrationClient),
WithSTSClientProvider(stsClt),
)
require.NoError(t, err)
creds, err := cfg.Credentials.Retrieve(ctx)
require.NoError(t, err)
require.Equal(t, "mock-access-key-id", creds.AccessKeyID)
require.Equal(t, "mock-secret-access-key", creds.SecretAccessKey)
require.Equal(t, "mock-session-token", creds.SessionToken)
})
}
type mockRolesAnywhereClient struct {
getIntegrationFn func(context.Context, string) (types.Integration, error)
}
func (f *mockRolesAnywhereClient) GetIntegration(ctx context.Context, name string) (types.Integration, error) {
return f.getIntegrationFn(ctx, name)
}
func (m *mockRolesAnywhereClient) GenerateAWSRACredentials(ctx context.Context, req *integrationpb.GenerateAWSRACredentialsRequest) (*integrationpb.GenerateAWSRACredentialsResponse, error) {
return &integrationpb.GenerateAWSRACredentialsResponse{
Expiration: timestamppb.New(time.Now().Add(1 * time.Hour)),
AccessKeyId: "mock-access-key-id",
SecretAccessKey: "mock-secret-access-key",
SessionToken: "mock-session-token",
}, nil
}
func TestNewCacheKey(t *testing.T) {
@@ -275,12 +332,35 @@ func TestNewCacheKey(t *testing.T) {
{RoleARN: "roleA"},
{RoleARN: "roleB", ExternalID: "abc123", SessionName: "alice", Tags: map[string]string{"AKey": "AValue"}},
}
got, err := newCacheKey("integration-name", roleChain...)
require.NoError(t, err)
want := strings.TrimSpace(`
{"integration":"integration-name","role_chain":[{"role_arn":"roleA"},{"role_arn":"roleB","external_id":"abc123","session_name":"alice","tags":{"AKey":"AValue"}}]}
t.Run("aws oidc integration", func(t *testing.T) {
t.Parallel()
got, err := newCacheKey("integration-name", RolesAnywhereMetadata{}, roleChain...)
require.NoError(t, err)
want := strings.TrimSpace(`
{"integration":"integration-name","role_chain":[{"role_arn":"roleA"},{"role_arn":"roleB","external_id":"abc123","session_name":"alice","tags":{"AKey":"AValue"}}],"roles_anywhere_integration_metadata":{"ProfileARN":"","ProfileAcceptsRoleSessionName":false,"RoleARN":"","IdentityUsername":"","SessionDuration":0}}
`)
require.Equal(t, want, got)
require.Equal(t, want, got)
})
t.Run("aws roles anywhere integration", func(t *testing.T) {
t.Parallel()
rolesAnywhereMetadata := RolesAnywhereMetadata{
ProfileARN: "my-profile-arn",
ProfileAcceptsRoleSessionName: true,
RoleARN: "arn:aws:iam::123456789012:role/role",
IdentityUsername: "alice",
SessionDuration: time.Hour,
}
got, err := newCacheKey("integration-name", rolesAnywhereMetadata, roleChain...)
require.NoError(t, err)
want := strings.TrimSpace(`
{"integration":"integration-name","role_chain":[{"role_arn":"roleA"},{"role_arn":"roleB","external_id":"abc123","session_name":"alice","tags":{"AKey":"AValue"}}],"roles_anywhere_integration_metadata":{"ProfileARN":"my-profile-arn","ProfileAcceptsRoleSessionName":true,"RoleARN":"arn:aws:iam::123456789012:role/role","IdentityUsername":"alice","SessionDuration":3600000000000}}
`)
require.Equal(t, want, got)
})
}
type fakeOIDCIntegrationClient struct {
+9 -7
View File
@@ -105,7 +105,7 @@ func (c *Cache) getBaseConfig(ctx context.Context, region string, opts *options)
// loading.
// We cache the entire config by integration name, which is empty for
// non-integration config, but only use credentials from it on cache hit.
cacheKey, err := newCacheKey(opts.integration)
cacheKey, err := newCacheKey(opts.integration, opts.rolesAnywhereIntegrationMetadata)
if err != nil {
return aws.Config{}, trace.Wrap(err)
}
@@ -136,7 +136,7 @@ func (c *Cache) getBaseConfig(ctx context.Context, region string, opts *options)
func (c *Cache) getConfigForRoleChain(ctx context.Context, cfg aws.Config, opts *options) (aws.Config, error) {
for i, r := range opts.assumeRoles {
// cache credentials by integration and assumed-role chain.
cacheKey, err := newCacheKey(opts.integration, opts.assumeRoles[:i+1]...)
cacheKey, err := newCacheKey(opts.integration, opts.rolesAnywhereIntegrationMetadata, opts.assumeRoles[:i+1]...)
if err != nil {
return aws.Config{}, trace.Wrap(err)
}
@@ -164,14 +164,16 @@ func (c *Cache) getConfigForRoleChain(ctx context.Context, cfg aws.Config, opts
// The cache key can be used to get role credentials without calling AWS STS.
// Therefore, we marshal the key as JSON to be sure the input cannot be
// manipulated to retrieve other credentials.
func newCacheKey(integrationName string, roleChain ...AssumeRole) (string, error) {
func newCacheKey(integrationName string, rolesAnywhereIntegrationMetadata RolesAnywhereMetadata, roleChain ...AssumeRole) (string, error) {
type configCacheKey struct {
Integration string `json:"integration"`
RoleChain []AssumeRole `json:"role_chain"`
Integration string `json:"integration"`
RoleChain []AssumeRole `json:"role_chain"`
RolesAnywhereIntegrationMetadata RolesAnywhereMetadata `json:"roles_anywhere_integration_metadata"`
}
out, err := json.Marshal(configCacheKey{
Integration: integrationName,
RoleChain: roleChain,
Integration: integrationName,
RoleChain: roleChain,
RolesAnywhereIntegrationMetadata: rolesAnywhereIntegrationMetadata,
})
return string(out), trace.Wrap(err)
}
@@ -136,8 +136,8 @@ type Credentials struct {
SecretAccessKey string `json:"SecretAccessKey"`
// SessionToken is the the AWS session token for temporary credentials.
SessionToken string `json:"SessionToken"`
// Expiration is ISO8601 timestamp string when the credentials expire.
Expiration string `json:"Expiration"`
// Expiration is the timestamp when the credentials expire.
Expiration time.Time `json:"Expiration"`
// SerialNumber is the serial number of the certificate which was created and exchanged to obtain AWS Credentials.
// When using these credentials, CloudTrail will log the certificate's Subject Common Name, if the profile accepts it.
// Otherwise, the serial number is logged.
@@ -223,12 +223,17 @@ func GenerateCredentials(ctx context.Context, req GenerateCredentialsRequest) (*
return nil, trace.BadParameter("failed to create session %v", err)
}
parsedExpiration, err := time.Parse(time.RFC3339, createSessionResp.Expiration)
if err != nil {
return nil, trace.BadParameter("failed to parse expiration time %q: %v", createSessionResp.Expiration, err)
}
return &Credentials{
Version: createSessionResp.Version,
AccessKeyID: createSessionResp.AccessKeyID,
SecretAccessKey: createSessionResp.SecretAccessKey,
SessionToken: createSessionResp.SessionToken,
Expiration: createSessionResp.Expiration,
Expiration: parsedExpiration,
SerialNumber: x509Cert.SerialNumber.String(),
}, nil
}
+1
View File
@@ -4940,6 +4940,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
ServiceComponent: teleport.ComponentWebProxy,
AWSConfigOptions: []awsconfig.OptionsFn{
awsconfig.WithOIDCIntegrationClient(conn.Client),
awsconfig.WithRolesAnywhereIntegrationClient(conn.Client),
},
})
if err != nil {
+1 -1
View File
@@ -176,7 +176,7 @@ func (s *signerHandler) serveCommonRequest(sessCtx *common.SessionContext, w htt
ExternalID: sessCtx.App.GetAWSExternalID(),
SessionName: sessCtx.Identity.Username,
}),
awsconfig.WithCredentialsMaybeIntegration(sessCtx.App.GetIntegration()),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: sessCtx.App.GetIntegration()}),
)
if err != nil {
return trace.Wrap(err)
+80 -39
View File
@@ -38,6 +38,7 @@ import (
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/lib/cloud/awsconfig"
"github.com/gravitational/teleport/lib/integrations/awsra"
"github.com/gravitational/teleport/lib/tlsca"
awsutils "github.com/gravitational/teleport/lib/utils/aws"
)
@@ -62,6 +63,9 @@ type AWSSigninRequest struct {
// Integration is the Integration name to use to generate credentials.
// If empty, it will use ambient credentials
Integration string
// RolesAnywhereMetadata contains the Profile/Role information to use when
// sourcing the credentials from a Roles Anywhere integration.
RolesAnywhereMetadata awsconfig.RolesAnywhereMetadata
}
// CheckAndSetDefaults validates the request.
@@ -177,16 +181,41 @@ func (c *cloud) getAWSSigninToken(ctx context.Context, req *AWSSigninRequest, en
// "SessionDuration" is not provided, the web console session duration will
// be bound to the duration used in the next AssumeRole call.
integrationMetadata := awsconfig.IntegrationMetadata{
Name: req.Integration,
RolesAnywhereMetadata: req.RolesAnywhereMetadata,
}
// When using Roles Anywhere integration, the session duration is set to the maximum allowed for temporary sessions: 1h.
// TODO(marco): add support for longer sessions which Roles Anywhere allows for but, requires us to know the Role's maximum session duration.
if req.RolesAnywhereMetadata.ProfileARN != "" {
duration, err := c.getFederationDuration(req, true /* temporarySession */)
if err != nil {
return "", trace.Wrap(err)
}
req.RolesAnywhereMetadata.SessionDuration = duration
}
// Sign In requests target IAM endpoints which don't require a region.
region := ""
baseCfg, err := c.awsCachedProvider.GetConfig(ctx, region,
awsconfig.WithCredentialsMaybeIntegration(req.Integration),
awsconfig.WithCredentialsMaybeIntegration(integrationMetadata),
)
if err != nil {
return "", trace.Wrap(err)
}
temporarySession, err := isSessionUsingTemporaryCredentials(ctx, baseCfg)
if baseCfg.Credentials == nil {
return "", trace.NotFound("session credentials not found")
}
baseCreds, err := baseCfg.Credentials.Retrieve(ctx)
if err != nil {
return "", trace.Wrap(err)
}
temporarySession, err := isSessionUsingTemporaryCredentials(baseCreds)
if err != nil {
return "", trace.Wrap(err)
}
@@ -196,39 +225,28 @@ func (c *cloud) getAWSSigninToken(ctx context.Context, req *AWSSigninRequest, en
return "", trace.Wrap(err)
}
assumeRole := awsconfig.AssumeRole{
RoleARN: req.Identity.RouteToApp.AWSRoleARN,
ExternalID: req.ExternalID,
// Setting role session name to Teleport username will allow to
// associate CloudTrail events with the Teleport user.
SessionName: req.Identity.Username,
}
awsConfigOptions := append(c.cfg.AWSConfigOptions,
awsconfig.WithCredentialsMaybeIntegration(integrationMetadata),
awsconfig.WithBaseCredentialsProvider(baseCfg.Credentials),
)
// Setting web console session duration through AssumeRole call for AWS
// sessions with temporary credentials.
// Technically the session duration can be set this way for
// non-temporary sessions. However, the AssumeRole call will fail if we
// are requesting duration longer than the maximum session duration of
// the role we are assuming. In addition, the session credentials may
// not have permission to perform a get-role on the role. Therefore,
// "SessionDuration" parameter will be defined when calling federation
// endpoint below instead of here, for non-temporary sessions.
// Most flows for providing AWS Access, require the following:
// - access to credentials for a helper Role (EC2 instance profile's Role, AWS OIDC Integration Role, etc.)
// - use the credentials to call sts:AssumeRole to obtain the credentials for the target role
// - use the credentials to call the federation endpoint to obtain the federation URL
//
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
if temporarySession {
c.cfg.Logger.DebugContext(ctx, "Temporary session")
assumeRole.Duration = duration
// The exception is the IAM Roles Anywhere integration which only uses the target role (no intermediate role).
switch {
case req.RolesAnywhereMetadata.ProfileARN != "":
default:
awsConfigOptions = append(awsConfigOptions,
getAssumeDetailedRolesOption(ctx, req, temporarySession, duration),
)
}
// Do not use cache provider to avoid returning credentials with wrong
// expiry duration.
awsCfg, err := awsconfig.GetConfig(ctx, region,
append(c.cfg.AWSConfigOptions,
awsconfig.WithCredentialsMaybeIntegration(req.Integration),
awsconfig.WithBaseCredentialsProvider(baseCfg.Credentials),
awsconfig.WithDetailedAssumeRole(assumeRole),
)...,
)
awsCfg, err := awsconfig.GetConfig(ctx, region, awsConfigOptions...)
if err != nil {
return "", trace.Wrap(err)
}
@@ -285,19 +303,38 @@ func (c *cloud) getAWSSigninToken(ctx context.Context, req *AWSSigninRequest, en
return fedResp.SigninToken, nil
}
func getAssumeDetailedRolesOption(ctx context.Context, req *AWSSigninRequest, temporarySession bool, duration time.Duration) awsconfig.OptionsFn {
assumeRole := awsconfig.AssumeRole{
RoleARN: req.Identity.RouteToApp.AWSRoleARN,
ExternalID: req.ExternalID,
// Setting role session name to Teleport username will allow to
// associate CloudTrail events with the Teleport user.
SessionName: req.Identity.Username,
}
// Setting web console session duration through AssumeRole call for AWS
// sessions with temporary credentials.
// Technically the session duration can be set this way for
// non-temporary sessions. However, the AssumeRole call will fail if we
// are requesting duration longer than the maximum session duration of
// the role we are assuming. In addition, the session credentials may
// not have permission to perform a get-role on the role. Therefore,
// "SessionDuration" parameter will be defined when calling federation
// endpoint below instead of here, for non-temporary sessions.
//
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
if temporarySession {
assumeRole.Duration = duration
}
return awsconfig.WithDetailedAssumeRole(assumeRole)
}
// isSessionUsingTemporaryCredentials checks if the current aws session is
// using temporary credentials.
//
// https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html
func isSessionUsingTemporaryCredentials(ctx context.Context, cfg aws.Config) (bool, error) {
if cfg.Credentials == nil {
return false, trace.NotFound("session credentials not found")
}
credentials, err := cfg.Credentials.Retrieve(ctx)
if err != nil {
return false, trace.Wrap(err)
}
func isSessionUsingTemporaryCredentials(credentials aws.Credentials) (bool, error) {
switch credentials.Source {
case ec2rolecreds.ProviderName:
@@ -320,7 +357,11 @@ func isSessionUsingTemporaryCredentials(ctx context.Context, cfg aws.Config) (bo
// ssocreds.Provider is an AWS credential provider that retrieves
// temporary AWS credentials by exchanging an SSO login token.
// https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials/ssocreds#Provider
ssocreds.ProviderName:
ssocreds.ProviderName,
// When using the AWS Roles Anywhere integration, the credentials are temporary:
// https://docs.aws.amazon.com/rolesanywhere/latest/userguide/authentication-create-session.html#response-elements
awsra.AWSCredentialsSourceRolesAnywhere:
return true, nil
}
+4 -13
View File
@@ -84,25 +84,16 @@ func TestIsSessionUsingTemporaryCredentials(t *testing.T) {
},
expectBool: true,
},
{
name: "bad config",
credentials: nil,
expectError: trace.IsNotFound,
},
{
name: "failed to get credentials",
credentials: &mockCredentialsProvider{
retrieveError: trace.AccessDenied(""),
},
expectError: trace.IsAccessDenied,
},
}
for _, test := range tests {
// capture range variable
t.Run(test.name, func(t *testing.T) {
t.Parallel()
isTemporary, err := isSessionUsingTemporaryCredentials(ctx, aws.Config{Credentials: test.credentials})
awsCredentials, err := test.credentials.Retrieve(ctx)
require.NoError(t, err)
isTemporary, err := isSessionUsingTemporaryCredentials(awsCredentials)
if test.expectError != nil {
require.True(t, test.expectError(err))
+6
View File
@@ -481,6 +481,12 @@ func (c *ConnectionsHandler) serveAWSWebConsole(w http.ResponseWriter, r *http.R
Issuer: app.GetPublicAddr(),
ExternalID: app.GetAWSExternalID(),
Integration: app.GetIntegration(),
RolesAnywhereMetadata: awsconfig.RolesAnywhereMetadata{
ProfileARN: app.GetAWSRolesAnywhereProfileARN(),
ProfileAcceptsRoleSessionName: app.GetAWSRolesAnywhereAcceptRoleSessionName(),
RoleARN: identity.RouteToApp.AWSRoleARN,
IdentityUsername: identity.Username,
},
})
if err != nil {
return trace.Wrap(err)
+1 -1
View File
@@ -858,7 +858,7 @@ func consumeTillErr(stream accessgraphv1alpha.AccessGraphService_AWSCloudTrailSt
func getOptions(matcher *types.AccessGraphAWSSync) []awsconfig.OptionsFn {
opts := []awsconfig.OptionsFn{
awsconfig.WithCredentialsMaybeIntegration(matcher.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: matcher.Integration}),
}
if matcher.AssumeRole != nil {
opts = append(opts, awsconfig.WithAssumeRole(matcher.AssumeRole.RoleARN, matcher.AssumeRole.ExternalID))
+1 -1
View File
@@ -1118,7 +1118,7 @@ func (s *Server) handleEC2RemoteInstallation(instances *server.EC2Instances) err
// TODO(gavin): support assume_role_arn for ec2.
ssmClient, err := s.GetSSMClient(s.ctx,
instances.Region,
awsconfig.WithCredentialsMaybeIntegration(instances.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: instances.Integration}),
)
if err != nil {
return trace.Wrap(err)
@@ -368,7 +368,7 @@ func (a *Fetcher) poll(ctx context.Context, features Features) (*Resources, erro
// with the v2 sdk.
func (a *Fetcher) getAWSOptions() []awsconfig.OptionsFn {
opts := []awsconfig.OptionsFn{
awsconfig.WithCredentialsMaybeIntegration(a.Config.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: a.Config.Integration}),
}
if a.Config.AssumeRole != nil {
+1 -1
View File
@@ -49,7 +49,7 @@ func (f *rdsDocumentDBFetcher) ComponentShortName() string {
func (f *rdsDocumentDBFetcher) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -59,7 +59,7 @@ func (f *elastiCachePlugin) ComponentShortName() string {
func (f *elastiCachePlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -56,7 +56,7 @@ func (f *memoryDBPlugin) ComponentShortName() string {
func (f *memoryDBPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -56,7 +56,7 @@ func (f *openSearchPlugin) ComponentShortName() string {
func (f *openSearchPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
+2 -2
View File
@@ -60,7 +60,7 @@ func (f *rdsDBInstancesPlugin) ComponentShortName() string {
func (f *rdsDBInstancesPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -157,7 +157,7 @@ func (f *rdsAuroraClustersPlugin) ComponentShortName() string {
func (f *rdsAuroraClustersPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -49,7 +49,7 @@ func (f *rdsDBProxyPlugin) ComponentShortName() string {
func (f *rdsDBProxyPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -49,7 +49,7 @@ type redshiftPlugin struct{}
func (f *redshiftPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
@@ -65,7 +65,7 @@ func (f *redshiftServerlessPlugin) ComponentShortName() string {
func (f *redshiftServerlessPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig) (types.Databases, error) {
awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region,
awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID),
awsconfig.WithCredentialsMaybeIntegration(cfg.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: cfg.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)
+1 -1
View File
@@ -738,7 +738,7 @@ func (a *eksFetcher) getAWSOpts() []awsconfig.OptionsFn {
a.AssumeRole.RoleARN,
a.AssumeRole.ExternalID,
),
awsconfig.WithCredentialsMaybeIntegration(a.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: a.Integration}),
}
}
+1 -1
View File
@@ -200,7 +200,7 @@ func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatc
for _, region := range matcher.Regions {
// TODO(gavin): support assume_role_arn for ec2.
ec2Client, err := getEC2Client(ctx, region,
awsconfig.WithCredentialsMaybeIntegration(matcher.Integration),
awsconfig.WithCredentialsMaybeIntegration(awsconfig.IntegrationMetadata{Name: matcher.Integration}),
)
if err != nil {
return nil, trace.Wrap(err)