Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,18 @@ For each authentication method, the try order is:
```go
// Using wokload identity federation flow
config.WithWorkloadIdentityFederationAuth()
// With the custom path for the external OIDC token
// With the external OIDC token
config.WithWorkloadIdentityFederationToken("OIDC Token")
// OR With the custom path for the external OIDC token
config.WithWorkloadIdentityFederationPath("/path/to/your/federated/token")
// For the service account
config.WithServiceAccountEmail("my-sa@sa-stackit.cloud")
```
**B. Environment Variables**
```bash
# Preferred: provide the external OIDC token directly
# (has priority over STACKIT_FEDERATED_TOKEN_FILE)
STACKIT_FEDERATED_TOKEN=<oidc-jwt-token>
# With the custom path for the external OIDC token
STACKIT_FEDERATED_TOKEN_FILE=/path/to/your/federated/token
# For the service account
Expand Down
10 changes: 9 additions & 1 deletion core/clients/workload_identity_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
Expand All @@ -15,6 +16,7 @@ import (

const (
clientIDEnv = "STACKIT_SERVICE_ACCOUNT_EMAIL"
FederatedTokenEnv = "STACKIT_FEDERATED_TOKEN" //nolint:gosec // This is not a secret, just the env variable name
FederatedTokenFileEnv = "STACKIT_FEDERATED_TOKEN_FILE" //nolint:gosec // This is not a secret, just the env variable name
wifTokenEndpointEnv = "STACKIT_IDP_TOKEN_ENDPOINT" //nolint:gosec // This is not a secret, just the env variable name
wifTokenExpirationEnv = "STACKIT_IDP_TOKEN_EXPIRATION_SECONDS" //nolint:gosec // This is not a secret, just the env variable name
Expand Down Expand Up @@ -134,7 +136,13 @@ func (c *WorkloadIdentityFederationFlow) Init(cfg *WorkloadIdentityFederationFlo
}

if c.config.FederatedTokenFunction == nil {
c.config.FederatedTokenFunction = oidcadapters.ReadJWTFromFileSystem(utils.GetEnvOrDefault(FederatedTokenFileEnv, defaultFederatedTokenPath))
if token, ok := os.LookupEnv(FederatedTokenEnv); ok {
c.config.FederatedTokenFunction = func(_ context.Context) (string, error) {
return token, nil
}
} else {
c.config.FederatedTokenFunction = oidcadapters.ReadJWTFromFileSystem(utils.GetEnvOrDefault(FederatedTokenFileEnv, defaultFederatedTokenPath))
}
}

c.tokenExpirationLeeway = defaultTokenExpirationLeeway
Expand Down
51 changes: 50 additions & 1 deletion core/clients/workload_identity_flow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func TestWorkloadIdentityFlowInit(t *testing.T) {
customTokenUrlEnv bool
tokenExpiration string
validAssertion bool
federatedTokenAsEnv bool
tokenFilePathEnv string
tokenFilePathAsEnv bool
missingTokenFilePath bool
wantErr bool
Expand Down Expand Up @@ -53,6 +55,19 @@ func TestWorkloadIdentityFlowInit(t *testing.T) {
validAssertion: true,
wantErr: false,
},
{
name: "ok using federated token from env",
clientID: "test@stackit.cloud",
federatedTokenAsEnv: true,
wantErr: false,
},
{
name: "federated token env has priority over invalid token file",
clientID: "test@stackit.cloud",
federatedTokenAsEnv: true,
tokenFilePathEnv: "/tmp/not-existing-token-file",
wantErr: false,
},
{
name: "missing client id",
validAssertion: true,
Expand Down Expand Up @@ -87,7 +102,19 @@ func TestWorkloadIdentityFlowInit(t *testing.T) {
flowConfig.TokenExpiration = tt.tokenExpiration
}

if !tt.missingTokenFilePath {
if tt.federatedTokenAsEnv {
token, err := signTokenWithSubject("subject", time.Minute)
if err != nil {
t.Fatalf("failed to create token: %v", err)
}
t.Setenv("STACKIT_FEDERATED_TOKEN", token)
}

if tt.tokenFilePathEnv != "" {
t.Setenv("STACKIT_FEDERATED_TOKEN_FILE", tt.tokenFilePathEnv)
}

if !tt.missingTokenFilePath && !tt.federatedTokenAsEnv {
file, err := os.CreateTemp("", "*.token")
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -118,6 +145,17 @@ func TestWorkloadIdentityFlowInit(t *testing.T) {
if err := flow.Init(flowConfig); (err != nil) != tt.wantErr {
t.Errorf("KeyFlow.Init() error = %v, wantErr %v", err, tt.wantErr)
}

if tt.federatedTokenAsEnv && !tt.wantErr {
tokenFromConfig, err := flow.config.FederatedTokenFunction(context.Background())
if err != nil {
t.Fatalf("getting federated token from config: %v", err)
}
tokenFromEnv := os.Getenv("STACKIT_FEDERATED_TOKEN")
if tokenFromConfig != tokenFromEnv {
t.Errorf("federated token mismatch, want env token")
}
}
if flow.config == nil {
t.Error("config is nil")
}
Expand Down Expand Up @@ -156,6 +194,7 @@ func TestWorkloadIdentityFlowRoundTrip(t *testing.T) {
clientID string
validAssertion bool
injectToken bool
tokenAsEnv bool
wantErr bool
}{
{
Expand All @@ -177,6 +216,13 @@ func TestWorkloadIdentityFlowRoundTrip(t *testing.T) {
validAssertion: false,
wantErr: true,
},
{
name: "token from env ok",
clientID: "test@stackit.cloud",
validAssertion: true,
tokenAsEnv: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -268,6 +314,9 @@ func TestWorkloadIdentityFlowRoundTrip(t *testing.T) {
flowConfig.FederatedTokenFunction = func(context.Context) (string, error) {
return token, nil
}
} else if tt.tokenAsEnv {
t.Setenv("STACKIT_FEDERATED_TOKEN", token)
t.Setenv("STACKIT_FEDERATED_TOKEN_FILE", "/tmp/not-existing-token-file")
} else {
file, err := os.CreateTemp("", "*.token")
if err != nil {
Expand Down
Loading