Skip to content

Commit

Permalink
[v14][buddy] Truncate AssumeRole session name to API limits (#45657)
Browse files Browse the repository at this point in the history
* [buddy] Truncate AssumeRole session name to API limits (#45202)

* 44833 Truncate AssumeRole session name to API limits

* Link reference

Co-authored-by: STeve (Xin) Huang <[email protected]>

* Hash the username and add audit log

* review comments

* remove audit

---------

Co-authored-by: Joao Ubaldo <[email protected]>

* reintroduce logrus

---------

Co-authored-by: Joao Ubaldo <[email protected]>
  • Loading branch information
greedy52 and joaoubaldo authored Aug 21, 2024
1 parent cafb1ef commit 8b33bfe
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/srv/app/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 34 additions & 0 deletions lib/utils/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package aws
import (
"bytes"
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"net/http"
"net/textproto"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
logrus.Debugf("AWS role session name %q is too long. Using a hash %q instead.", roleSessionName, ret)
return ret
}
30 changes: 30 additions & 0 deletions lib/utils/aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "[email protected]",
},
} {
t.Run(tt.name, func(t *testing.T) {
actual := MaybeHashRoleSessionName(tt.role)
require.Equal(t, tt.expected, actual)
require.LessOrEqual(t, len(actual), MaxRoleSessionNameLength)
})
}
}
2 changes: 1 addition & 1 deletion lib/utils/aws/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down

0 comments on commit 8b33bfe

Please sign in to comment.