diff --git a/lib/srv/app/cloud.go b/lib/srv/app/cloud.go index 210cc40b96d38..75bc989f185b9 100644 --- a/lib/srv/app/cloud.go +++ b/lib/srv/app/cloud.go @@ -182,7 +182,7 @@ func (c *cloud) getAWSSigninToken(req *AWSSigninRequest, endpoint string, option options = append(options, func(creds *stscreds.AssumeRoleProvider) { // Setting role session name to Teleport username will allow to // associate CloudTrail events with the Teleport user. - creds.RoleSessionName = req.Identity.Username + creds.RoleSessionName = awsutils.MaybeHashRoleSessionName(req.Identity.Username) // Setting web console session duration through AssumeRole call for AWS // sessions with temporary credentials. diff --git a/lib/utils/aws/aws.go b/lib/utils/aws/aws.go index be9d5365f686e..f7d4acc270769 100644 --- a/lib/utils/aws/aws.go +++ b/lib/utils/aws/aws.go @@ -19,7 +19,10 @@ package aws import ( "bytes" "context" + "crypto/sha1" + "encoding/hex" "fmt" + "log/slog" "net/http" "net/textproto" "sort" @@ -31,7 +34,6 @@ import ( v4 "github.com/aws/aws-sdk-go/aws/signer/v4" "github.com/aws/aws-sdk-go/service/iam" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" apievents "github.com/gravitational/teleport/api/types/events" apiawsutils "github.com/gravitational/teleport/api/utils/aws" @@ -66,6 +68,11 @@ const ( AmzJSON1_0 = "application/x-amz-json-1.0" // AmzJSON1_1 is an AWS Content-Type header that indicates the media type is JSON. AmzJSON1_1 = "application/x-amz-json-1.1" + + // MaxRoleSessionNameLength is the maximum length of the role session name + // used by the AssumeRole call. + // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html + MaxRoleSessionNameLength = 64 ) // SigV4 contains parsed content of the AWS Authorization header. @@ -247,7 +254,7 @@ func FilterAWSRoles(arns []string, accountID string) (result Roles) { for _, roleARN := range arns { parsed, err := ParseRoleARN(roleARN) if err != nil { - logrus.Warnf("skipping invalid AWS role ARN: %v", err) + slog.WarnContext(context.Background(), "Skipping invalid AWS role ARN.", "error", err) continue } if accountID != "" && parsed.AccountID != accountID { @@ -479,3 +486,30 @@ func iamResourceARN(partition, accountID, resourceType, resourceName string) str Resource: fmt.Sprintf("%s/%s", resourceType, resourceName), }.String() } + +// MaybeHashRoleSessionName truncates the role session name and adds a hash +// when the original role session name is greater than AWS character limit +// (64). +func MaybeHashRoleSessionName(roleSessionName string) (ret string) { + if len(roleSessionName) <= MaxRoleSessionNameLength { + return roleSessionName + } + + const hashLen = 16 + hash := sha1.New() + hash.Write([]byte(roleSessionName)) + hex := hex.EncodeToString(hash.Sum(nil))[:hashLen] + + // "1" for the delimiter. + keepPrefixIndex := MaxRoleSessionNameLength - len(hex) - 1 + + // Sanity check. This should never happen since hash length and + // MaxRoleSessionNameLength are both constant. + if keepPrefixIndex < 0 { + keepPrefixIndex = 0 + } + + ret = fmt.Sprintf("%s-%s", roleSessionName[:keepPrefixIndex], hex) + slog.DebugContext(context.Background(), "AWS role session name is too long. Using a hash instead.", "hashed", ret, "original", roleSessionName) + return ret +} diff --git a/lib/utils/aws/aws_test.go b/lib/utils/aws/aws_test.go index cb99e41abf0aa..a97d5bc8824b1 100644 --- a/lib/utils/aws/aws_test.go +++ b/lib/utils/aws/aws_test.go @@ -487,3 +487,33 @@ func TestResourceARN(t *testing.T) { }) } } + +func TestMaybeHashRoleSessionName(t *testing.T) { + for _, tt := range []struct { + name string + role string + expected string + }{ + { + name: "role session name not hashed, less than 64 characters", + role: "MyRole", + expected: "MyRole", + }, + { + name: "role session name not hashed, exactly 64 characters", + role: "Role123456789012345678901234567890123456789012345678901234567890", + expected: "Role123456789012345678901234567890123456789012345678901234567890", + }, + { + name: "role session name hashed, longer than 64 characters", + role: "remote-raimundo.oliveira@abigcompany.com-teleport.abigcompany.com", + expected: "remote-raimundo.oliveira@abigcompany.com-telepo-8fe1f87e599b043e", + }, + } { + t.Run(tt.name, func(t *testing.T) { + actual := MaybeHashRoleSessionName(tt.role) + require.Equal(t, tt.expected, actual) + require.LessOrEqual(t, len(actual), MaxRoleSessionNameLength) + }) + } +} diff --git a/lib/utils/aws/credentials.go b/lib/utils/aws/credentials.go index 9a4c67854eb3e..42b34e07908d9 100644 --- a/lib/utils/aws/credentials.go +++ b/lib/utils/aws/credentials.go @@ -66,7 +66,7 @@ func (g *credentialsGetter) Get(_ context.Context, request GetCredentialsRequest logrus.Debugf("Creating STS session %q for %q.", request.SessionName, request.RoleARN) return stscreds.NewCredentials(request.Provider, request.RoleARN, func(cred *stscreds.AssumeRoleProvider) { - cred.RoleSessionName = request.SessionName + cred.RoleSessionName = MaybeHashRoleSessionName(request.SessionName) cred.Expiry.SetExpiration(request.Expiry, 0) if request.ExternalID != "" {