Skip to content

Commit

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

* Link reference



* Hash the username and add audit log

* review comments

* remove audit

---------

Co-authored-by: Joao Ubaldo <[email protected]>
  • Loading branch information
greedy52 and joaoubaldo authored Aug 21, 2024
1 parent 4558f28 commit 4f2b340
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 4 deletions.
2 changes: 1 addition & 1 deletion lib/srv/app/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (c *cloud) getAWSSigninToken(ctx context.Context, req *AWSSigninRequest, en
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
38 changes: 36 additions & 2 deletions lib/utils/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ package aws
import (
"bytes"
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"log/slog"
"net/http"
"net/textproto"
"sort"
Expand All @@ -33,7 +36,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"
Expand Down Expand Up @@ -68,6 +70,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 @@ -249,7 +256,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 {
Expand Down Expand Up @@ -484,3 +491,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
}
30 changes: 30 additions & 0 deletions lib/utils/aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,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 @@ -74,7 +74,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 4f2b340

Please sign in to comment.