From fb9b033f4a6469d2eef8c3f0a75fbd3eef090e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Mon, 12 May 2025 17:20:16 +0200 Subject: [PATCH 1/8] fix(key_flow): Add 5 second leeway to refresh access tokens early --- core/clients/key_flow.go | 11 ++++++++--- core/clients/key_flow_test.go | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/clients/key_flow.go b/core/clients/key_flow.go index c529a6f80..4c8e1ae57 100644 --- a/core/clients/key_flow.go +++ b/core/clients/key_flow.go @@ -32,6 +32,7 @@ const ( tokenAPI = "https://service-account.api.stackit.cloud/token" //nolint:gosec // linter false positive defaultTokenType = "Bearer" defaultScope = "" + tokenExpirationLeeway = time.Second * 5 ) // KeyFlow handles auth with SA key @@ -400,11 +401,15 @@ func tokenExpired(token string) (bool, error) { if err != nil { return false, fmt.Errorf("parse token: %w", err) } + expirationTimestampNumeric, err := tokenParsed.Claims.GetExpirationTime() if err != nil { return false, fmt.Errorf("get expiration timestamp: %w", err) } - expirationTimestamp := expirationTimestampNumeric.Time - now := time.Now() - return now.After(expirationTimestamp), nil + + // Pretend to be `tokenExpirationLeeway` into the future to avoid token expiring + // between retrieving the token and using it in the actual request. + now := time.Now().Add(tokenExpirationLeeway) + + return now.After(expirationTimestampNumeric.Time), nil } diff --git a/core/clients/key_flow_test.go b/core/clients/key_flow_test.go index b37b9593f..d03d2c51c 100644 --- a/core/clients/key_flow_test.go +++ b/core/clients/key_flow_test.go @@ -209,6 +209,12 @@ func TestTokenExpired(t *testing.T) { expectedErr: false, expectedIsExpired: true, }, + { + desc: "token almost expired", + tokenExpiresAt: time.Now().Add(tokenExpirationLeeway), + expectedErr: false, + expectedIsExpired: true, + }, { desc: "token invalid", tokenInvalid: true, From 809f92d62671b2753086f8aaf6f2b891c2d23ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Mon, 12 May 2025 17:24:18 +0200 Subject: [PATCH 2/8] clarify comment --- core/clients/key_flow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/clients/key_flow.go b/core/clients/key_flow.go index 4c8e1ae57..d1040a49c 100644 --- a/core/clients/key_flow.go +++ b/core/clients/key_flow.go @@ -408,7 +408,7 @@ func tokenExpired(token string) (bool, error) { } // Pretend to be `tokenExpirationLeeway` into the future to avoid token expiring - // between retrieving the token and using it in the actual request. + // between retrieving the token and upstream systems validating it. now := time.Now().Add(tokenExpirationLeeway) return now.After(expirationTimestampNumeric.Time), nil From f52884a8741142bb98205b8f31053c5dbff654f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Tue, 13 May 2025 12:49:43 +0200 Subject: [PATCH 3/8] move leeway to KeyFlow struct and pass to tokenExpired func --- core/clients/key_flow.go | 19 +++++++++++++++---- core/clients/key_flow_test.go | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/clients/key_flow.go b/core/clients/key_flow.go index d1040a49c..589774314 100644 --- a/core/clients/key_flow.go +++ b/core/clients/key_flow.go @@ -32,7 +32,8 @@ const ( tokenAPI = "https://service-account.api.stackit.cloud/token" //nolint:gosec // linter false positive defaultTokenType = "Bearer" defaultScope = "" - tokenExpirationLeeway = time.Second * 5 + + defaultTokenExpirationLeeway = time.Second * 5 ) // KeyFlow handles auth with SA key @@ -46,6 +47,10 @@ type KeyFlow struct { tokenMutex sync.RWMutex token *TokenResponseBody + + // If the current access token would expire in less than TokenExpirationLeeway, + // the client will refresh it early to prevent clock skew or other timing issues. + tokenExpirationLeeway time.Duration } // KeyFlowConfig is the flow config @@ -130,6 +135,8 @@ func (c *KeyFlow) Init(cfg *KeyFlowConfig) error { c.config.TokenUrl = tokenAPI } + c.tokenExpirationLeeway = defaultTokenExpirationLeeway + if c.rt = cfg.HTTPTransport; c.rt == nil { c.rt = http.DefaultTransport } @@ -205,7 +212,7 @@ func (c *KeyFlow) GetAccessToken() (string, error) { } c.tokenMutex.RUnlock() - accessTokenExpired, err := tokenExpired(accessToken) + accessTokenExpired, err := tokenExpired(accessToken, c.tokenExpirationLeeway) if err != nil { return "", fmt.Errorf("check access token is expired: %w", err) } @@ -253,6 +260,10 @@ func (c *KeyFlow) validate() error { } c.privateKeyPEM = pem.EncodeToMemory(privKeyPEM) + if c.tokenExpirationLeeway < 0 { + return fmt.Errorf("token expiration leeway cannot be negative") + } + return nil } @@ -269,7 +280,7 @@ func (c *KeyFlow) recreateAccessToken() error { } c.tokenMutex.RUnlock() - refreshTokenExpired, err := tokenExpired(refreshToken) + refreshTokenExpired, err := tokenExpired(refreshToken, c.tokenExpirationLeeway) if err != nil { return err } @@ -390,7 +401,7 @@ func (c *KeyFlow) parseTokenResponse(res *http.Response) error { return nil } -func tokenExpired(token string) (bool, error) { +func tokenExpired(token string, tokenExpirationLeeway time.Duration) (bool, error) { if token == "" { return true, nil } diff --git a/core/clients/key_flow_test.go b/core/clients/key_flow_test.go index d03d2c51c..78dc43a3b 100644 --- a/core/clients/key_flow_test.go +++ b/core/clients/key_flow_test.go @@ -190,6 +190,7 @@ func TestSetToken(t *testing.T) { } func TestTokenExpired(t *testing.T) { + tokenExpirationLeeway := 5 * time.Second tests := []struct { desc string tokenInvalid bool @@ -235,7 +236,7 @@ func TestTokenExpired(t *testing.T) { } } - isExpired, err := tokenExpired(token) + isExpired, err := tokenExpired(token, tokenExpirationLeeway) if err != nil && !tt.expectedErr { t.Fatalf("failed on valid input: %v", err) } From 9f9cd6b28267958cbe4f6395ddf930f380a0848d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Thu, 22 May 2025 13:13:45 +0200 Subject: [PATCH 4/8] Update changelogs --- CHANGELOG.md | 5 +++++ core/CHANGELOG.md | 3 +++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e781a17..59853f635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Release (2025-YY-XX) +- core: + - v0.17.2 + - Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses + ## Release (2025-05-15) - `alb`: - [v0.4.0](services/alb/CHANGELOG.md#v040-2025-05-15) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index c38237322..578accfd2 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.17.2 (2025-05-22) +- Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses + ## v0.17.1 (2025-04-09) - **Improvement:** Improve error message for key flow authentication From be3f068f44990a4f615dd295c42c5da5394ca263 Mon Sep 17 00:00:00 2001 From: Patrick <118536155+ptrckmllr@users.noreply.github.com> Date: Thu, 22 May 2025 13:34:19 +0200 Subject: [PATCH 5/8] Update CHANGELOG.md Co-authored-by: Alexander Dahmen --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59853f635..87dc78b37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ ## Release (2025-YY-XX) -- core: - v0.17.2 - Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses From 33327648658efd14145d355d99972f78650474f6 Mon Sep 17 00:00:00 2001 From: Patrick <118536155+ptrckmllr@users.noreply.github.com> Date: Thu, 22 May 2025 13:34:40 +0200 Subject: [PATCH 6/8] Update CHANGELOG.md Co-authored-by: Alexander Dahmen --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87dc78b37..7b8e57697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Release (2025-YY-XX) - - v0.17.2 +- `core`: [v0.17.2](core/CHANGELOG.md#v0172-2025-05-22) - Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses ## Release (2025-05-15) From 91990f2c9f4d021c419d2bc1380a757247e94fd0 Mon Sep 17 00:00:00 2001 From: Patrick <118536155+ptrckmllr@users.noreply.github.com> Date: Thu, 22 May 2025 13:35:01 +0200 Subject: [PATCH 7/8] Update CHANGELOG.md Co-authored-by: Alexander Dahmen --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8e57697..741755226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Release (2025-YY-XX) - `core`: [v0.17.2](core/CHANGELOG.md#v0172-2025-05-22) - - Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses + - **Bugfix:** Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses ## Release (2025-05-15) - `alb`: From f24ea589cb04342aa752bafc8c3388bcf0c3fee8 Mon Sep 17 00:00:00 2001 From: Patrick <118536155+ptrckmllr@users.noreply.github.com> Date: Thu, 22 May 2025 13:35:08 +0200 Subject: [PATCH 8/8] Update core/CHANGELOG.md Co-authored-by: Alexander Dahmen --- core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 578accfd2..5b070c901 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,5 @@ ## v0.17.2 (2025-05-22) -- Bugfix: Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses +- **Bugfix:** Access tokens generated via key flow authentication are refreshed 5 seconds before expiration to prevent timing issues with upstream systems which could lead to unexpected 401 error responses ## v0.17.1 (2025-04-09) - **Improvement:** Improve error message for key flow authentication