-
Notifications
You must be signed in to change notification settings - Fork 25
feat: Support workload identity federation flow #4074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
91fc4a8
3a0b673
60a09f7
f8da09d
85275f2
711502e
7dd73c1
ed9d1b4
796fb6d
dc427ec
6e3169f
28d4a02
61b2cd4
76a1610
845a48f
f540b0b
675a8b5
5e41dc3
027987b
60c10fb
265f147
1da4767
b9f0948
fdc732d
f12d244
c9c107c
9112482
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -234,4 +234,4 @@ See the [release documentation](./RELEASE.md) for further information. | |
|
|
||
| ## License | ||
|
|
||
| Apache 2.0 | ||
| Apache 2.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## v0.21.0 | ||
| - **Chore:** Use `jwt-bearer` grant to get a fresh token instead of `refresh_token` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add here the information, that RefreshAccessToken is deprecated and will be removed in the future |
||
| - **Feature:** Support Workload Identity Federation flow | ||
|
|
||
| ## v0.20.1 | ||
| - **Improvement:** Improve error message when passing a PEM encoded file to as service account key | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v0.20.1 | ||
| v0.21.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package clients | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "time" | ||
|
|
||
| "github.com/golang-jwt/jwt/v5" | ||
| "github.com/stackitcloud/stackit-sdk-go/core/oapierror" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultTokenExpirationLeeway = time.Second * 5 | ||
| ) | ||
|
|
||
| type AuthFlow interface { | ||
| RoundTrip(req *http.Request) (*http.Response, error) | ||
| GetAccessToken() (string, error) | ||
| RefreshAccessToken() error | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a deprecation to all methods which relates to the RefreshAccessToken. This makes also the cleanup in 6 months easier |
||
| GetBackgroundTokenRefreshContext() context.Context | ||
| } | ||
|
|
||
| // TokenResponseBody is the API response | ||
| // when requesting a new token | ||
| type TokenResponseBody struct { | ||
| AccessToken string `json:"access_token"` | ||
| ExpiresIn int `json:"expires_in"` | ||
JorTurFer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Deprecated: RefreshToken is no longer used and the SDK will not attempt to refresh tokens using it but will instead use the AuthFlow implementation to get new tokens. | ||
| // This will be removed after 2026-07-01. | ||
| RefreshToken string `json:"refresh_token"` | ||
| Scope string `json:"scope"` | ||
| TokenType string `json:"token_type"` | ||
| } | ||
|
|
||
| func parseTokenResponse(res *http.Response) (*TokenResponseBody, error) { | ||
| if res == nil { | ||
| return nil, fmt.Errorf("received bad response from API") | ||
| } | ||
| if res.StatusCode != http.StatusOK { | ||
| body, err := io.ReadAll(res.Body) | ||
| if err != nil { | ||
| // Fail silently, omit body from error | ||
| // We're trying to show error details, so it's unnecessary to fail because of this err | ||
| body = []byte{} | ||
| } | ||
| return nil, &oapierror.GenericOpenAPIError{ | ||
| StatusCode: res.StatusCode, | ||
| Body: body, | ||
| } | ||
| } | ||
| body, err := io.ReadAll(res.Body) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| token := &TokenResponseBody{} | ||
| err = json.Unmarshal(body, token) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unmarshal token response: %w", err) | ||
| } | ||
| return token, nil | ||
| } | ||
|
|
||
| func tokenExpired(token string, tokenExpirationLeeway time.Duration) (bool, error) { | ||
| if token == "" { | ||
| return true, nil | ||
| } | ||
|
|
||
| // We can safely use ParseUnverified because we are not authenticating the user at this point. | ||
| // We're just checking the expiration time | ||
| tokenParsed, _, err := jwt.NewParser().ParseUnverified(token, &jwt.RegisteredClaims{}) | ||
| 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) | ||
| } | ||
|
|
||
| // Pretend to be `tokenExpirationLeeway` into the future to avoid token expiring | ||
| // between retrieving the token and upstream systems validating it. | ||
| now := time.Now().Add(tokenExpirationLeeway) | ||
| return now.After(expirationTimestampNumeric.Time), nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And add the same comment here