Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 68 additions & 62 deletions lib/auth/join_iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ const (
// ever have a need to allow a newer API version.
expectedSTSIdentityRequestBody = "Action=GetCallerIdentity&Version=2011-06-15"

// Used to check if we were unable to resolve the regional STS endpoint.
globalSTSEndpoint = "https://sts.amazonaws.com"

// AWS SignedHeaders will always be lowercase
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html#sigv4-auth-header-overview
challengeHeaderKey = "x-teleport-challenge"
Expand Down Expand Up @@ -371,10 +368,42 @@ func (a *Server) RegisterUsingIAMMethod(ctx context.Context, challengeResponse c
return certs, trace.Wrap(err)
}

type stsIdentityRequestConfig struct {
regionalEndpointOption endpoints.STSRegionalEndpoint
fipsEndpointOption endpoints.FIPSEndpointState
}

type stsIdentityRequestOption func(cfg *stsIdentityRequestConfig)

func withRegionalEndpoint(useRegionalEndpoint bool) stsIdentityRequestOption {
return func(cfg *stsIdentityRequestConfig) {
if useRegionalEndpoint {
cfg.regionalEndpointOption = endpoints.RegionalSTSEndpoint
} else {
cfg.regionalEndpointOption = endpoints.LegacySTSEndpoint
}
}
}

func withFIPSEndpoint(useFIPS bool) stsIdentityRequestOption {
return func(cfg *stsIdentityRequestConfig) {
if useFIPS {
cfg.fipsEndpointOption = endpoints.FIPSEndpointStateEnabled
} else {
cfg.fipsEndpointOption = endpoints.FIPSEndpointStateDisabled
}
}
}

// createSignedSTSIdentityRequest is called on the client side and returns an
// sts:GetCallerIdentity request signed with the local AWS credentials
func createSignedSTSIdentityRequest(ctx context.Context, endpointOption stsEndpointOption, challenge string) ([]byte, error) {
stsClient, err := endpointOption(ctx)
func createSignedSTSIdentityRequest(ctx context.Context, challenge string, opts ...stsIdentityRequestOption) ([]byte, error) {
cfg := &stsIdentityRequestConfig{}
for _, opt := range opts {
opt(cfg)
}

stsClient, err := newSTSClient(ctx, cfg)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -396,75 +425,52 @@ func createSignedSTSIdentityRequest(ctx context.Context, endpointOption stsEndpo
return signedRequest.Bytes(), nil
}

type stsEndpointOption func(context.Context) (*sts.STS, error)

var (
stsEndpointOptionGlobal = newGlobalSTSClient
stsEndpointOptionRegional = newRegionalSTSClient
)

// newRegionalSTSClient returns an STS client will resolve the "global" endpoint
// for the STS service.
func newGlobalSTSClient(ctx context.Context) (*sts.STS, error) {
// sess will be used as a ConfigProvider to be passed to sts.New. It will
// load AWS configuration options from the environment, which means that AWS
// credentials may come from environment variables, files in ~/.aws/, or
// from the attached role on an EC2 instance.
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return nil, trace.Wrap(err)
func newSTSClient(ctx context.Context, cfg *stsIdentityRequestConfig) (*sts.STS, error) {
awsConfig := awssdk.Config{
UseFIPSEndpoint: cfg.fipsEndpointOption,
STSRegionalEndpoint: cfg.regionalEndpointOption,
}
return sts.New(sess), nil
}

// newRegionalSTSClient returns an STS client which attempts to resolve the local
// regional endpoint for the STS service, rather than the "global" endpoint
// which is not supported in non-default AWS partitions.
func newRegionalSTSClient(ctx context.Context) (*sts.STS, error) {
// sess will be used as a ConfigProvider to be passed to sts.New. It will
// load AWS configuration options from the environment, which means that AWS
// credentials may come from environment variables, files in ~/.aws/, or
// from the attached role on an EC2 instance. The regional STS endpoint will
// be used instead of the global endopint if the local (or preferred) region
// can be resolved from the environment.
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *awssdk.NewConfig().WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint),
Config: awsConfig,
})
if err != nil {
return nil, trace.Wrap(err)
}

// will set the local region on extraConfigOptions if we can find it from
// the environment or IMDS
extraConfigOptions := awssdk.NewConfig()

// If the region was not resolved from the environment the client will try to
// use the global STS endpoint, which will not be supported if the AWS identity
// being used is for a non-default AWS partition (such as China or
// GovCloud.) This is the default behavior on EC2, so let's try to find the
// region from the IMDS.
if clientConfig := sess.ClientConfig(sts.ServiceName); clientConfig.Endpoint == globalSTSEndpoint {
region, err := getEC2LocalRegion(ctx)
if trace.IsNotFound(err) {
// Unfortunately we could not find the region from the IMDS, go with
// the default global endpoint and hope it works.
log.Info("Unable to find the local AWS region from the environment or IMDSv2. " +
"Attempting to use the global STS endpoint for the IAM join method. " +
"This will probably fail in non-default AWS partitions such as China or GovCloud. " +
"Consider setting the AWS_REGION environment variable, setting the region in ~/.aws/config, or enabling the IMDSv2.")
} else if err != nil {
// Return the unexpected error.
return nil, trace.Wrap(err)
stsClient := sts.New(sess)

if apiutils.SliceContainsStr(globalSTSEndpoints, strings.TrimPrefix(stsClient.Endpoint, "https://")) {
// If the caller wants to use the regional endpoint but it was not resolved
// from the environment, attempt to find the region from the EC2 IMDS
if cfg.regionalEndpointOption == endpoints.RegionalSTSEndpoint {
region, err := getEC2LocalRegion(ctx)
if err != nil {
return nil, trace.Wrap(err, "failed to resolve local AWS region from environment or IMDS")
}
stsClient = sts.New(sess, awssdk.NewConfig().WithRegion(region))
} else {
// Found the region, set it on the config.
extraConfigOptions.Region = &region
log.Info("Attempting to use the global STS endpoint for the IAM join method. " +
"This will probably fail in non-default AWS partitions such as China or GovCloud, or if FIPS mode is enabled. " +
"Consider setting the AWS_REGION environment variable, setting the region in ~/.aws/config, or enabling the IMDSv2.")
}
}

return sts.New(sess, extraConfigOptions), nil
if cfg.fipsEndpointOption == endpoints.FIPSEndpointStateEnabled &&
!apiutils.SliceContainsStr(validSTSEndpoints, strings.TrimPrefix(stsClient.Endpoint, "https://")) {
// The AWS SDK will generate invalid endpoints when attempting to
// resolve the FIPS endpoint for a region which does not have one.
// In this case, try to use the FIPS endpoint in us-east-1. This should
// work for all regions in the standard partition. In GovCloud we should
// not hit this because all regional endpoints support FIPS. In China or
// other partitions this will fail and FIPS mode will not be supported.
log.Infof("AWS SDK resolved FIPS STS endpoint %s, which does not appear to be valid. "+
"Attempting to use the FIPS STS endpoint for us-east-1.",
stsClient.Endpoint)
stsClient = sts.New(sess, awssdk.NewConfig().WithRegion("us-east-1"))
}

return stsClient, nil
}

// getEC2LocalRegion returns the AWS region this EC2 instance is running in, or
Expand Down
19 changes: 15 additions & 4 deletions lib/auth/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ type RegisterParams struct {
ec2IdentityDocument []byte
// CircuitBreakerConfig defines how the circuit breaker should behave.
CircuitBreakerConfig breaker.Config
// FIPS means FedRAMP/FIPS 140-2 compliant configuration was requested.
FIPS bool
}

func (r *RegisterParams) setDefaults() {
Expand Down Expand Up @@ -470,22 +472,31 @@ func registerUsingIAMMethod(joinServiceClient joinServiceClient, token string, p
var errs []error
for _, s := range []struct {
desc string
opt stsEndpointOption
opts []stsIdentityRequestOption
}{
{
desc: "regional",
opt: stsEndpointOptionRegional,
opts: []stsIdentityRequestOption{
withFIPSEndpoint(params.FIPS),
withRegionalEndpoint(true),
},
},
{
desc: "global",
opt: stsEndpointOptionGlobal,
opts: []stsIdentityRequestOption{
// Global endpoint does not support FIPS, this is a fallback
// when joining a cluster with an auth server on an older
// version which does not yet support regional endpoints.
withFIPSEndpoint(false),
withRegionalEndpoint(false),
},
},
} {
log.Infof("Attempting to register %s with IAM method using %s STS endpoint", params.ID.Role, s.desc)
// Call RegisterUsingIAMMethod and pass a callback to respond to the challenge with a signed join request.
certs, err := joinServiceClient.RegisterUsingIAMMethod(ctx, func(challenge string) (*proto.RegisterUsingIAMMethodRequest, error) {
// create the signed sts:GetCallerIdentity request and include the challenge
signedRequest, err := createSignedSTSIdentityRequest(ctx, s.opt, challenge)
signedRequest, err := createSignedSTSIdentityRequest(ctx, challenge, s.opts...)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
67 changes: 67 additions & 0 deletions lib/auth/sts_endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2022 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 auth

var (
// validSTSEndpoints holds a sorted list of all known valid public endpoints for
// the AWS STS service. You can generate this list by running
// $ go run github.com/nklaassen/sts-endpoints@latest --go-list
// Update aws-sdk-go in that package to learn about new endpoints.
validSTSEndpoints = []string{
"sts-fips.us-east-1.amazonaws.com",
"sts-fips.us-east-2.amazonaws.com",
"sts-fips.us-west-1.amazonaws.com",
"sts-fips.us-west-2.amazonaws.com",
"sts.af-south-1.amazonaws.com",
"sts.amazonaws.com",
"sts.ap-east-1.amazonaws.com",
"sts.ap-northeast-1.amazonaws.com",
"sts.ap-northeast-2.amazonaws.com",
"sts.ap-northeast-3.amazonaws.com",
"sts.ap-south-1.amazonaws.com",
"sts.ap-southeast-1.amazonaws.com",
"sts.ap-southeast-2.amazonaws.com",
"sts.ap-southeast-3.amazonaws.com",
"sts.ca-central-1.amazonaws.com",
"sts.cn-north-1.amazonaws.com.cn",
"sts.cn-northwest-1.amazonaws.com.cn",
"sts.eu-central-1.amazonaws.com",
"sts.eu-north-1.amazonaws.com",
"sts.eu-south-1.amazonaws.com",
"sts.eu-west-1.amazonaws.com",
"sts.eu-west-2.amazonaws.com",
"sts.eu-west-3.amazonaws.com",
"sts.me-south-1.amazonaws.com",
"sts.sa-east-1.amazonaws.com",
"sts.us-east-1.amazonaws.com",
"sts.us-east-2.amazonaws.com",
"sts.us-gov-east-1.amazonaws.com",
"sts.us-gov-west-1.amazonaws.com",
"sts.us-iso-east-1.c2s.ic.gov",
"sts.us-iso-west-1.c2s.ic.gov",
"sts.us-isob-east-1.sc2s.sgov.gov",
"sts.us-west-1.amazonaws.com",
"sts.us-west-2.amazonaws.com",
}

globalSTSEndpoints = []string{
"sts.amazonaws.com",
// This is not a real endpoint, but the SDK will select it if
// AWS_USE_FIPS_ENDPOINT is set and a region is not.
"sts-fips.aws-global.amazonaws.com",
}
)
58 changes: 0 additions & 58 deletions lib/auth/valid_sts_endpoints.go

This file was deleted.

1 change: 1 addition & 0 deletions lib/service/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ func (process *TeleportProcess) firstTimeConnect(role types.SystemRole) (*Connec
Clock: process.Clock,
JoinMethod: process.Config.JoinMethod,
CircuitBreakerConfig: process.Config.CircuitBreakerConfig,
FIPS: process.Config.FIPS,
})
if err != nil {
if utils.IsUntrustedCertErr(err) {
Expand Down