Skip to content

Commit 182ff35

Browse files
committed
add --use-oidc flag
1 parent 17e3ea5 commit 182ff35

6 files changed

Lines changed: 115 additions & 6 deletions

File tree

AUTHENTICATION.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,14 @@ Using this flow is less secure since the token is long-lived. You can provide th
114114
- Navigate to `Service Accounts` → Select account → `Federated Identity Providers`
115115
- [Configure a Federated Identity Provider](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-federations/#create-a-federated-identity-provider) and the required assertions. For detailed assertion configuration per platform, see the [Terraform provider WIF guide](https://github.com/stackitcloud/terraform-provider-stackit/blob/main/docs/guides/workload_identity_federation.md).
116116

117-
2. Configure authentication using environment variables:
117+
2. Configure authentication for `stackit auth activate-service-account` using one of the options below:
118+
119+
- Explicit flag: `--use-oidc` (takes precedence)
120+
- Environment variable: `STACKIT_USE_OIDC=1`
121+
122+
If both are provided, the explicit flag value is used.
123+
124+
Example using environment variables:
118125

119126
```bash
120127
STACKIT_USE_OIDC=1

docs/stackit_auth_activate-service-account.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ stackit auth activate-service-account [flags]
2727
Only print the corresponding access token by using the service account token. This access token can be stored as environment variable (STACKIT_ACCESS_TOKEN) in order to be used for all subsequent commands.
2828
$ stackit auth activate-service-account --service-account-token my-service-account-token --only-print-access-token
2929
30-
Authenticate via Workload Identity Federation (OIDC) and print the short-lived access token. Set STACKIT_USE_OIDC=1 and STACKIT_SERVICE_ACCOUNT_EMAIL; no service account key file is required.
31-
$ STACKIT_USE_OIDC=1 STACKIT_SERVICE_ACCOUNT_EMAIL=ci@sa.stackit.cloud stackit auth activate-service-account --only-print-access-token
30+
Authenticate via Workload Identity Federation (OIDC) and print the short-lived access token. Use --use-oidc to explicitly enable OIDC (takes precedence over STACKIT_USE_OIDC); no service account key file is required.
31+
$ STACKIT_SERVICE_ACCOUNT_EMAIL=ci@sa.stackit.cloud stackit auth activate-service-account --use-oidc --only-print-access-token
3232
```
3333

3434
### Options
@@ -39,6 +39,7 @@ stackit auth activate-service-account [flags]
3939
--private-key-path string RSA private key path. It takes precedence over the private key included in the service account key, if present
4040
--service-account-key-path string Service account key path
4141
--service-account-token string Service account long-lived access token
42+
--use-oidc Use Workload Identity Federation (OIDC). If set, this takes precedence over STACKIT_USE_OIDC
4243
```
4344

4445
### Options inherited from parent commands

internal/cmd/auth/activate-service-account/activate_service_account.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@ const (
2525
serviceAccountTokenFlag = "service-account-token"
2626
serviceAccountKeyPathFlag = "service-account-key-path"
2727
privateKeyPathFlag = "private-key-path"
28+
useOIDCFlag = "use-oidc"
2829
onlyPrintAccessTokenFlag = "only-print-access-token" // #nosec G101
2930
)
3031

3132
type inputModel struct {
3233
ServiceAccountToken string
3334
ServiceAccountKeyPath string
3435
PrivateKeyPath string
36+
UseOIDC *bool
3537
OnlyPrintAccessToken bool
3638
}
3739

@@ -60,8 +62,8 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
6062
"$ stackit auth activate-service-account --service-account-token my-service-account-token --only-print-access-token",
6163
),
6264
examples.NewExample(
63-
`Authenticate via Workload Identity Federation (OIDC) and print the short-lived access token. Set STACKIT_USE_OIDC=1 and STACKIT_SERVICE_ACCOUNT_EMAIL; no service account key file is required.`,
64-
"$ STACKIT_USE_OIDC=1 STACKIT_SERVICE_ACCOUNT_EMAIL=ci@sa.stackit.cloud stackit auth activate-service-account --only-print-access-token",
65+
`Authenticate via Workload Identity Federation (OIDC) and print the short-lived access token. Use --use-oidc to explicitly enable OIDC (takes precedence over STACKIT_USE_OIDC); no service account key file is required.`,
66+
"$ STACKIT_SERVICE_ACCOUNT_EMAIL=ci@sa.stackit.cloud stackit auth activate-service-account --use-oidc --only-print-access-token",
6567
),
6668
),
6769
RunE: func(cmd *cobra.Command, args []string) error {
@@ -71,7 +73,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
7173
}
7274

7375
// use workload identity federation (OIDC) if enabled; no key file required
74-
if auth.IsOIDCEnabled() {
76+
if auth.IsOIDCEnabledWithOverride(model.UseOIDC) {
7577
return runOIDCMode(params, model)
7678
}
7779

@@ -124,6 +126,7 @@ func configureFlags(cmd *cobra.Command) {
124126
cmd.Flags().String(serviceAccountTokenFlag, "", "Service account long-lived access token")
125127
cmd.Flags().String(serviceAccountKeyPathFlag, "", "Service account key path")
126128
cmd.Flags().String(privateKeyPathFlag, "", "RSA private key path. It takes precedence over the private key included in the service account key, if present")
129+
cmd.Flags().Bool(useOIDCFlag, false, "Use Workload Identity Federation (OIDC). If set, this takes precedence over STACKIT_USE_OIDC")
127130
cmd.Flags().Bool(onlyPrintAccessTokenFlag, false, "If this is set to true the credentials are not stored in either the keyring or a file")
128131
}
129132

@@ -134,6 +137,10 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel,
134137
PrivateKeyPath: flags.FlagToStringValue(p, cmd, privateKeyPathFlag),
135138
OnlyPrintAccessToken: flags.FlagToBoolValue(p, cmd, onlyPrintAccessTokenFlag),
136139
}
140+
if cmd.Flags().Changed(useOIDCFlag) {
141+
useOIDC := flags.FlagToBoolValue(p, cmd, useOIDCFlag)
142+
model.UseOIDC = &useOIDC
143+
}
137144

138145
p.DebugInputModel(model)
139146
return &model, nil

internal/cmd/auth/activate-service-account/activate_service_account_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import (
1414

1515
var testTokenCustomEndpoint = "token_url"
1616

17+
func boolPtr(v bool) *bool {
18+
return &v
19+
}
20+
1721
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
1822
flagValues := map[string]string{
1923
serviceAccountTokenFlag: "token",
2024
serviceAccountKeyPathFlag: "sa_key",
2125
privateKeyPathFlag: "private_key",
26+
useOIDCFlag: "true",
2227
onlyPrintAccessTokenFlag: "true",
2328
}
2429
for _, mod := range mods {
@@ -32,6 +37,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
3237
ServiceAccountToken: "token",
3338
ServiceAccountKeyPath: "sa_key",
3439
PrivateKeyPath: "private_key",
40+
UseOIDC: boolPtr(true),
3541
OnlyPrintAccessToken: true,
3642
}
3743
for _, mod := range mods {
@@ -65,6 +71,7 @@ func TestParseInput(t *testing.T) {
6571
ServiceAccountToken: "",
6672
ServiceAccountKeyPath: "",
6773
PrivateKeyPath: "",
74+
UseOIDC: nil,
6875
},
6976
},
7077
{
@@ -80,8 +87,29 @@ func TestParseInput(t *testing.T) {
8087
ServiceAccountToken: "",
8188
ServiceAccountKeyPath: "",
8289
PrivateKeyPath: "",
90+
UseOIDC: nil,
8391
},
8492
},
93+
{
94+
description: "use_oidc_true",
95+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
96+
flagValues[useOIDCFlag] = "true"
97+
}),
98+
isValid: true,
99+
expectedModel: fixtureInputModel(func(model *inputModel) {
100+
model.UseOIDC = boolPtr(true)
101+
}),
102+
},
103+
{
104+
description: "use_oidc_false",
105+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
106+
flagValues[useOIDCFlag] = "false"
107+
}),
108+
isValid: true,
109+
expectedModel: fixtureInputModel(func(model *inputModel) {
110+
model.UseOIDC = boolPtr(false)
111+
}),
112+
},
85113
{
86114
description: "invalid_flag",
87115
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
@@ -101,6 +129,18 @@ func TestParseInput(t *testing.T) {
101129
model.OnlyPrintAccessToken = false
102130
}),
103131
},
132+
{
133+
description: "default value UseOIDC",
134+
flagValues: fixtureFlagValues(
135+
func(flagValues map[string]string) {
136+
delete(flagValues, useOIDCFlag)
137+
},
138+
),
139+
isValid: true,
140+
expectedModel: fixtureInputModel(func(model *inputModel) {
141+
model.UseOIDC = nil
142+
}),
143+
},
104144
}
105145

106146
for _, tt := range tests {
@@ -164,3 +204,4 @@ func TestStoreCustomEndpointFlags(t *testing.T) {
164204
})
165205
}
166206
}
207+

internal/pkg/auth/oidc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ func IsOIDCEnabled() bool {
2222
return os.Getenv(EnvUseOIDC) == "1"
2323
}
2424

25+
// IsOIDCEnabledWithOverride resolves OIDC mode using explicit input first and env fallback.
26+
// If useOIDC is not nil, its value is used directly; otherwise STACKIT_USE_OIDC is evaluated.
27+
func IsOIDCEnabledWithOverride(useOIDC *bool) bool {
28+
if useOIDC != nil {
29+
return *useOIDC
30+
}
31+
32+
return IsOIDCEnabled()
33+
}
34+
2535
func OIDCServiceAccountEmail() string {
2636
return os.Getenv(EnvServiceAccountEmail)
2737
}

internal/pkg/auth/oidc_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
1111
)
1212

13+
func boolPtr(v bool) *bool {
14+
return &v
15+
}
16+
1317
func TestIsEnabled(t *testing.T) {
1418
tests := []struct {
1519
value string
@@ -41,6 +45,45 @@ func TestIsEnabled_Unset(t *testing.T) {
4145
}
4246
}
4347

48+
func TestIsOIDCEnabledWithOverride(t *testing.T) {
49+
tests := []struct {
50+
description string
51+
envUseOIDC string
52+
override *bool
53+
expected bool
54+
}{
55+
{
56+
description: "uses env when override is nil",
57+
envUseOIDC: "1",
58+
override: nil,
59+
expected: true,
60+
},
61+
{
62+
description: "override true wins over env false",
63+
envUseOIDC: "0",
64+
override: boolPtr(true),
65+
expected: true,
66+
},
67+
{
68+
description: "override false wins over env true",
69+
envUseOIDC: "1",
70+
override: boolPtr(false),
71+
expected: false,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.description, func(t *testing.T) {
77+
t.Setenv(auth.EnvUseOIDC, tt.envUseOIDC)
78+
79+
got := auth.IsOIDCEnabledWithOverride(tt.override)
80+
if got != tt.expected {
81+
t.Errorf("IsOIDCEnabledWithOverride() = %v, want %v", got, tt.expected)
82+
}
83+
})
84+
}
85+
}
86+
4487
func TestServiceAccountEmail(t *testing.T) {
4588
const want = "ci@sa.stackit.cloud"
4689
t.Setenv(auth.EnvServiceAccountEmail, want)

0 commit comments

Comments
 (0)