From 1ec562f09e9729206a9e8a3cfa0b367e25d39b00 Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 25 Nov 2025 15:53:30 +0000 Subject: [PATCH] awsutil(sts): add configurable session duration Allow user to specify the duration in minutes for the cloud console session to remain active, up to a maximum of 60 minutes. This duration controls how long the federated console session remains active, independent of the underlying STS credential expiration. Signed-off-by: Petr --- cmd/ocm-backplane/cloud/common.go | 2 +- cmd/ocm-backplane/cloud/console.go | 28 +++++++++++++++++++++++++--- pkg/awsutil/sts.go | 17 ++++++++++++++++- pkg/awsutil/sts_test.go | 2 +- pkg/cli/config/config.go | 1 + 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/cmd/ocm-backplane/cloud/common.go b/cmd/ocm-backplane/cloud/common.go index 911d722d..b26a36f7 100644 --- a/cmd/ocm-backplane/cloud/common.go +++ b/cmd/ocm-backplane/cloud/common.go @@ -99,7 +99,7 @@ func (cfg *QueryConfig) GetCloudConsole() (*ConsoleResponse, error) { return nil, fmt.Errorf("failed to get signin token: %w", err) } - signinFederationURL, err := awsutil.GetConsoleURL(resp.SigninToken, cfg.Cluster.Region().ID()) + signinFederationURL, err := awsutil.GetConsoleURL(resp.SigninToken, cfg.Cluster.Region().ID(), cfg.SessionDurationMinutes) if err != nil { return nil, fmt.Errorf("failed to generate console url: %w", err) } diff --git a/cmd/ocm-backplane/cloud/console.go b/cmd/ocm-backplane/cloud/console.go index 944e028d..3ef6abf5 100644 --- a/cmd/ocm-backplane/cloud/console.go +++ b/cmd/ocm-backplane/cloud/console.go @@ -18,9 +18,10 @@ import ( ) var consoleArgs struct { - browser bool - backplaneURL string - output string + browser bool + backplaneURL string + output string + sessionDurationMinutes int } type ConsoleResponse struct { @@ -37,6 +38,10 @@ func (r *ConsoleResponse) String() string { // EnvBrowserDefault environment variable that indicates if open by browser is set as default const EnvBrowserDefault = "BACKPLANE_DEFAULT_OPEN_BROWSER" +// MAX_SESSION_TIMEOUT_DURATION maximum session duration in minutes +// This is limited by AWS STS maximum duration for assumed roles of 60 minutes +const MAX_SESSION_TIMEOUT_DURATION = 60 // in minutes + // ConsoleCmd represents the cloud credentials command var ConsoleCmd = &cobra.Command{ Use: "console [CLUSTERID|EXTERNAL_ID|CLUSTER_NAME|CLUSTER_NAME_SEARCH]", @@ -74,6 +79,12 @@ func init() { "text", "Format the output of the console response.", ) + flags.IntVar( + &consoleArgs.sessionDurationMinutes, + "session-duration-minutes", + 0, + "Duration in minutes for the cloud console session to remain active (cannot exceed underlying STS credential expiration 60m, default 15m).", + ) } func runConsole(cmd *cobra.Command, argv []string) (err error) { @@ -126,6 +137,17 @@ func runConsole(cmd *cobra.Command, argv []string) (err error) { backplaneConfiguration.URL = consoleArgs.backplaneURL } + // Only evaluate if the session duration is greater than the default of 15 minutes + // The default does not need to be configured anywhere + if consoleArgs.sessionDurationMinutes > 15 { + if consoleArgs.sessionDurationMinutes > MAX_SESSION_TIMEOUT_DURATION { + logger.Warnf("Session duration cannot exceed %d minutes, setting to maximum of %d minutes", MAX_SESSION_TIMEOUT_DURATION, MAX_SESSION_TIMEOUT_DURATION) + backplaneConfiguration.SessionDurationMinutes = MAX_SESSION_TIMEOUT_DURATION + } else { + backplaneConfiguration.SessionDurationMinutes = consoleArgs.sessionDurationMinutes + } + } + logger.Infof("Using backplane URL: %s\n", backplaneConfiguration.URL) // Initialize OCM connection diff --git a/pkg/awsutil/sts.go b/pkg/awsutil/sts.go index 1af29c73..72660341 100644 --- a/pkg/awsutil/sts.go +++ b/pkg/awsutil/sts.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "strconv" "time" logger "github.com/sirupsen/logrus" @@ -82,6 +83,9 @@ func AssumeRoleWithJWT(jwt string, roleArn string, stsClient stscreds.AssumeRole IdentityTokenValue(jwt), func(options *stscreds.WebIdentityRoleOptions) { options.RoleSessionName = email + // Explicitly request a 60 minute session duration + // it is the default, but being explicit here for clarity + options.Duration = 60 * time.Minute }, )) @@ -102,6 +106,9 @@ func AssumeRole( ) (aws.Credentials, error) { assumeRoleProvider := stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(options *stscreds.AssumeRoleOptions) { options.RoleSessionName = roleSessionName + // Explicitly request a 60 minute session duration + // it is the default, but being explicit here for clarity + options.Duration = 60 * time.Minute if inlinePolicy != nil { options.Policy = aws.String(inlinePolicy.String()) } @@ -259,12 +266,20 @@ func GetSigninToken(awsCredentials aws.Credentials, region string) (*AWSSigninTo return &resp, nil } -func GetConsoleURL(signinToken string, region string) (*url.URL, error) { +func GetConsoleURL(signinToken string, region string, sessionDurationMinutes int) (*url.URL, error) { signinParams := url.Values{} signinParams.Add("Action", "login") signinParams.Add("Destination", fmt.Sprintf(AwsConsoleURLTemplate, region)) signinParams.Add("Issuer", DefaultIssuer) signinParams.Add("SigninToken", signinToken) + // Only include parameter if session duration is greater than AWS default of 15 minutes + // Explicitly set console session duration to requested time. + // This controls how long the federated console session remains active + // independent of the underlying STS credential expiration (cannot exceed 60 minutes) + if sessionDurationMinutes > 15 && sessionDurationMinutes <= 60 { + logger.Infof("sesstion_duration: %d minutes", sessionDurationMinutes) + signinParams.Add("SessionDuration", strconv.Itoa(sessionDurationMinutes*60)) + } signInFederationURL, err := url.Parse(fmt.Sprintf(AwsFederatedSigninEndpointTemplate, region)) if err != nil { diff --git a/pkg/awsutil/sts_test.go b/pkg/awsutil/sts_test.go index 511fbd25..6221dc6f 100644 --- a/pkg/awsutil/sts_test.go +++ b/pkg/awsutil/sts_test.go @@ -837,7 +837,7 @@ func TestGetConsoleUrl(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GetConsoleURL(tt.signinToken, "us-east-1") + got, err := GetConsoleURL(tt.signinToken, "us-east-1", 0) if (err != nil) != tt.wantErr { t.Errorf("GetConsoleURL() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/cli/config/config.go b/pkg/cli/config/config.go index 0aa6db96..b6d0c565 100644 --- a/pkg/cli/config/config.go +++ b/pkg/cli/config/config.go @@ -50,6 +50,7 @@ type BackplaneConfiguration struct { DisplayClusterInfo bool `json:"display-cluster-info"` DisableKubePS1Warning bool `json:"disable-kube-ps1-warning"` Govcloud bool `json:"govcloud"` + SessionDurationMinutes int `json:"session-duration-minutes"` } const (