Skip to content

Commit fb8f990

Browse files
authored
Fix/key auth environment variables (#1499)
* fix(auth): Key auth should take STACKIT_PRIVATE_KEY and STACKIT_SERVICE_ACCOUNT_KEY into account Those values can be supplied via environment and also via credentials file. Signed-off-by: Alexander Dahmen <[email protected]> * Add changelog Signed-off-by: Alexander Dahmen <[email protected]> --------- Signed-off-by: Alexander Dahmen <[email protected]>
1 parent de91cd7 commit fb8f990

File tree

5 files changed

+187
-44
lines changed

5 files changed

+187
-44
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Release (2025-XX-XX)
22

3+
- `core`: [v0.16.1](core/CHANGELOG.md#v0161-2025-02-25)
4+
- **Bugfix:** STACKIT_PRIVATE_KEY and STACKIT_SERVICE_ACCOUNT_KEY can be set via environment variable or via credentials file.
35
- `stackitmarketplace`: [v0.3.0](services/stackitmarketplace/CHANGELOG.md#v030-2025-02-25)
46
- **Feature:** Add method to create inquiries: `InquiriesCreateInquiry`
57
- **Feature:** Add `sort` property to `ApiListCatalogProductsRequest`

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ The SDK supports two authentication methods:
120120
The SDK searches for credentials in the following order:
121121

122122
1. Explicit configuration in code
123-
2. Environment variables
123+
2. Environment variables (KEY_PATH for KEY)
124124
3. Credentials file (`$HOME/.stackit/credentials.json`)
125125

126126
For each authentication method, the key flow is attempted first, followed by the token flow.

core/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## v0.16.1 (2025-02-25)
2+
3+
- **Bugfix:** STACKIT_PRIVATE_KEY and STACKIT_SERVICE_ACCOUNT_KEY can be set via environment variable or via credentials file.
4+
15
## v0.16.0 (2025-02-21)
26
- **New:** Minimal go version is now Go 1.21
37

core/auth/auth.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ type Credentials struct {
1818
STACKIT_SERVICE_ACCOUNT_TOKEN string
1919
STACKIT_SERVICE_ACCOUNT_KEY_PATH string
2020
STACKIT_PRIVATE_KEY_PATH string
21+
STACKIT_SERVICE_ACCOUNT_KEY string
22+
STACKIT_PRIVATE_KEY string
2123
}
2224

2325
const (
2426
credentialsFilePath = ".stackit/credentials.json" //nolint:gosec // linter false positive
2527
tokenCredentialType credentialType = "token"
28+
serviceAccountKeyCredentialType credentialType = "service_account_key"
2629
serviceAccountKeyPathCredentialType credentialType = "service_account_key_path"
30+
privateKeyCredentialType credentialType = "private_key"
2731
privateKeyPathCredentialType credentialType = "private_key_path"
2832
)
2933

@@ -239,6 +243,16 @@ func readCredential(cred credentialType, credentials *Credentials) (string, erro
239243
if credentialValue == "" {
240244
return credentialValue, fmt.Errorf("private key path is empty or not set")
241245
}
246+
case serviceAccountKeyCredentialType:
247+
credentialValue = credentials.STACKIT_SERVICE_ACCOUNT_KEY
248+
if credentialValue == "" {
249+
return credentialValue, fmt.Errorf("service account key is empty or not set")
250+
}
251+
case privateKeyCredentialType:
252+
credentialValue = credentials.STACKIT_PRIVATE_KEY
253+
if credentialValue == "" {
254+
return credentialValue, fmt.Errorf("private key is empty or not set")
255+
}
242256
default:
243257
return "", fmt.Errorf("invalid credential type: %s", cred)
244258
}
@@ -268,21 +282,36 @@ func getServiceAccountEmail(cfg *config.Configuration) string {
268282
}
269283

270284
// getKey searches for a key in the following order: client configuration, environment variable, credentials file.
271-
func getKey(cfgKey, cfgKeyPath *string, envVar, credType credentialType, cfgCredFilePath string) error {
285+
func getKey(cfgKey, cfgKeyPath *string, envVarKeyPath, envVarKey string, credTypePath, credTypeKey credentialType, cfgCredFilePath string) error {
272286
if *cfgKey != "" {
273287
return nil
274288
}
275289
if *cfgKeyPath == "" {
276-
keyPath, keyPathSet := os.LookupEnv(string(envVar))
277-
if !keyPathSet || keyPath == "" {
290+
// check environment variable: path
291+
keyPath, keyPathSet := os.LookupEnv(envVarKeyPath)
292+
// check environment variable: key
293+
key, keySet := os.LookupEnv(envVarKey)
294+
// if both are not set -> read from credentials file
295+
if (!keyPathSet || keyPath == "") && (!keySet || key == "") {
278296
credentials, err := readCredentialsFile(cfgCredFilePath)
279297
if err != nil {
280298
return fmt.Errorf("reading from credentials file: %w", err)
281299
}
282-
keyPath, err = readCredential(credType, credentials)
300+
// read key path from credentials file
301+
keyPath, err = readCredential(credTypePath, credentials)
283302
if err != nil || keyPath == "" {
284-
return fmt.Errorf("neither key nor path is provided in the configuration, environment variable, or credentials file: %w", err)
303+
// key path was not provided, read key from credentials file
304+
key, err = readCredential(credTypeKey, credentials)
305+
if err != nil || key == "" {
306+
return fmt.Errorf("neither key nor path is provided in the configuration, environment variable, or credentials file: %w", err)
307+
}
308+
*cfgKey = key
309+
return nil
285310
}
311+
} else if !keyPathSet || keyPath == "" {
312+
// key path was not provided, use key
313+
*cfgKey = key
314+
return nil
286315
}
287316
*cfgKeyPath = keyPath
288317
}
@@ -299,10 +328,10 @@ func getKey(cfgKey, cfgKeyPath *string, envVar, credType credentialType, cfgCred
299328

300329
// getServiceAccountKey configures the service account key in the provided configuration
301330
func getServiceAccountKey(cfg *config.Configuration) error {
302-
return getKey(&cfg.ServiceAccountKey, &cfg.ServiceAccountKeyPath, "STACKIT_SERVICE_ACCOUNT_KEY_PATH", serviceAccountKeyPathCredentialType, cfg.CredentialsFilePath)
331+
return getKey(&cfg.ServiceAccountKey, &cfg.ServiceAccountKeyPath, "STACKIT_SERVICE_ACCOUNT_KEY_PATH", "STACKIT_SERVICE_ACCOUNT_KEY", serviceAccountKeyPathCredentialType, serviceAccountKeyCredentialType, cfg.CredentialsFilePath)
303332
}
304333

305334
// getPrivateKey configures the private key in the provided configuration
306335
func getPrivateKey(cfg *config.Configuration) error {
307-
return getKey(&cfg.PrivateKey, &cfg.PrivateKeyPath, "STACKIT_PRIVATE_KEY_PATH", privateKeyPathCredentialType, cfg.CredentialsFilePath)
336+
return getKey(&cfg.PrivateKey, &cfg.PrivateKeyPath, "STACKIT_PRIVATE_KEY_PATH", "STACKIT_PRIVATE_KEY", privateKeyPathCredentialType, privateKeyCredentialType, cfg.CredentialsFilePath)
308337
}

core/auth/auth_test.go

Lines changed: 144 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ func fixtureServiceAccountKey(mods ...func(*clients.ServiceAccountKeyResponse))
5050
return serviceAccountKeyResponse
5151
}
5252

53+
// helper function to create a credentials.json file with saKey and private key
54+
func createCredentialsKeyJson(serviceAccountKey, privateKey string) ([]byte, error) {
55+
tempMap := map[string]interface{}{}
56+
tempMap["STACKIT_SERVICE_ACCOUNT_KEY"] = serviceAccountKey
57+
tempMap["STACKIT_PRIVATE_KEY"] = privateKey
58+
return json.Marshal(tempMap)
59+
}
60+
5361
// Error cases are tested in the NoAuth, KeyAuth, TokenAuth and DefaultAuth functions
5462
func TestSetupAuth(t *testing.T) {
5563
privateKey, err := generatePrivateKey()
@@ -111,57 +119,104 @@ func TestSetupAuth(t *testing.T) {
111119
}
112120
}()
113121

122+
// create a credentials file with saKey and private key
123+
credentialsKeyFile, errs := os.CreateTemp("", "temp-*.txt")
124+
if errs != nil {
125+
t.Fatalf("Creating temporary file: %s", err)
126+
}
127+
defer func() {
128+
err := os.Remove(credentialsKeyFile.Name())
129+
if err != nil {
130+
t.Fatalf("Removing temporary file: %s", err)
131+
}
132+
}()
133+
134+
credKeyJson, err := createCredentialsKeyJson(string(saKey), privateKey)
135+
if err != nil {
136+
t.Fatalf("createCredentialsKeyJson: %s", err)
137+
}
138+
_, errs = credentialsKeyFile.WriteString(string(credKeyJson))
139+
if errs != nil {
140+
t.Fatalf("Writing credentials json to temporary file: %s", err)
141+
}
142+
114143
for _, test := range []struct {
115-
desc string
116-
config *config.Configuration
117-
setToken bool
118-
setKeys bool
119-
setPath bool
120-
isValid bool
144+
desc string
145+
config *config.Configuration
146+
setToken bool
147+
setKeys bool
148+
setKeyPaths bool
149+
setCredentialsFilePathToken bool
150+
setCredentialsFilePathKey bool
151+
isValid bool
121152
}{
122153
{
123-
desc: "token_config",
124-
config: nil,
125-
setToken: true,
126-
setPath: false,
127-
isValid: true,
154+
desc: "token_config",
155+
config: nil,
156+
setToken: true,
157+
setCredentialsFilePathToken: false,
158+
isValid: true,
128159
},
129160
{
130-
desc: "key_config",
131-
config: nil,
132-
setKeys: true,
133-
setPath: false,
134-
isValid: true,
161+
desc: "key_config",
162+
config: nil,
163+
setKeys: true,
164+
setCredentialsFilePathToken: false,
165+
isValid: true,
135166
},
136167
{
137-
desc: "valid_path_to_file",
138-
config: nil,
139-
setToken: false,
140-
setPath: true,
141-
isValid: true,
168+
desc: "key_config_path",
169+
config: nil,
170+
setKeys: false,
171+
setKeyPaths: true,
172+
setCredentialsFilePathToken: false,
173+
isValid: true,
174+
},
175+
{
176+
desc: "key_config_credentials_path",
177+
config: nil,
178+
setKeys: false,
179+
setKeyPaths: false,
180+
setCredentialsFilePathKey: true,
181+
isValid: true,
182+
},
183+
{
184+
desc: "valid_path_to_file",
185+
config: nil,
186+
setToken: false,
187+
setCredentialsFilePathToken: true,
188+
isValid: true,
142189
},
143190
{
144191
desc: "custom_config_token",
145192
config: &config.Configuration{
146193
Token: "token",
147194
},
148-
setToken: false,
149-
setPath: false,
150-
isValid: true,
195+
setToken: false,
196+
setCredentialsFilePathToken: false,
197+
isValid: true,
151198
},
152199
{
153200
desc: "custom_config_path",
154201
config: &config.Configuration{
155202
CredentialsFilePath: "test_resources/test_credentials_bar.json",
156203
},
157-
setToken: false,
158-
setPath: false,
159-
isValid: true,
204+
setToken: false,
205+
setCredentialsFilePathToken: false,
206+
isValid: true,
160207
},
161208
} {
162209
t.Run(test.desc, func(t *testing.T) {
163210
setTemporaryHome(t)
164211
if test.setKeys {
212+
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", string(saKey))
213+
t.Setenv("STACKIT_PRIVATE_KEY", privateKey)
214+
} else {
215+
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", "")
216+
t.Setenv("STACKIT_PRIVATE_KEY", "")
217+
}
218+
219+
if test.setKeyPaths {
165220
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH", saKeyFile.Name())
166221
t.Setenv("STACKIT_PRIVATE_KEY_PATH", privateKeyFile.Name())
167222
} else {
@@ -175,8 +230,10 @@ func TestSetupAuth(t *testing.T) {
175230
t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "")
176231
}
177232

178-
if test.setPath {
233+
if test.setCredentialsFilePathToken {
179234
t.Setenv("STACKIT_CREDENTIALS_PATH", "test_resources/test_credentials_bar.json")
235+
} else if test.setCredentialsFilePathKey {
236+
t.Setenv("STACKIT_CREDENTIALS_PATH", credentialsKeyFile.Name())
180237
} else {
181238
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
182239
}
@@ -327,12 +384,35 @@ func TestDefaultAuth(t *testing.T) {
327384
}
328385
}()
329386

387+
// create a credentials file with saKey and private key
388+
credentialsKeyFile, errs := os.CreateTemp("", "temp-*.txt")
389+
if errs != nil {
390+
t.Fatalf("Creating temporary file: %s", err)
391+
}
392+
defer func() {
393+
err := os.Remove(credentialsKeyFile.Name())
394+
if err != nil {
395+
t.Fatalf("Removing temporary file: %s", err)
396+
}
397+
}()
398+
399+
credKeyJson, err := createCredentialsKeyJson(string(saKey), privateKey)
400+
if err != nil {
401+
t.Fatalf("createCredentialsKeyJson: %s", err)
402+
}
403+
_, errs = credentialsKeyFile.WriteString(string(credKeyJson))
404+
if errs != nil {
405+
t.Fatalf("Writing credentials json to temporary file: %s", err)
406+
}
407+
330408
for _, test := range []struct {
331-
desc string
332-
setToken bool
333-
setKeys bool
334-
isValid bool
335-
expectedFlow string
409+
desc string
410+
setToken bool
411+
setKeyPaths bool
412+
setKeys bool
413+
setCredentialsFilePathKey bool
414+
isValid bool
415+
expectedFlow string
336416
}{
337417
{
338418
desc: "token",
@@ -343,7 +423,7 @@ func TestDefaultAuth(t *testing.T) {
343423
{
344424
desc: "key_precedes_token",
345425
setToken: true,
346-
setKeys: true,
426+
setKeyPaths: true,
347427
isValid: true,
348428
expectedFlow: "key",
349429
},
@@ -352,23 +432,51 @@ func TestDefaultAuth(t *testing.T) {
352432
setToken: false,
353433
isValid: false,
354434
},
435+
{
436+
desc: "use keys via environment",
437+
setKeys: true,
438+
setToken: false,
439+
isValid: true,
440+
expectedFlow: "key",
441+
},
442+
{
443+
desc: "use keys via credentials file",
444+
setKeys: false,
445+
setToken: false,
446+
setCredentialsFilePathKey: true,
447+
isValid: true,
448+
expectedFlow: "key",
449+
},
355450
} {
356451
t.Run(test.desc, func(t *testing.T) {
357452
setTemporaryHome(t)
358-
if test.setKeys {
453+
if test.setKeyPaths {
359454
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH", saKeyFile.Name())
360455
t.Setenv("STACKIT_PRIVATE_KEY_PATH", privateKeyFile.Name())
361456
} else {
362457
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY_PATH", "")
363458
t.Setenv("STACKIT_PRIVATE_KEY_PATH", "")
364459
}
365460

461+
if test.setKeys {
462+
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", string(saKey))
463+
t.Setenv("STACKIT_PRIVATE_KEY", privateKey)
464+
} else {
465+
t.Setenv("STACKIT_SERVICE_ACCOUNT_KEY", "")
466+
t.Setenv("STACKIT_PRIVATE_KEY", "")
467+
}
468+
469+
if test.setCredentialsFilePathKey {
470+
t.Setenv("STACKIT_CREDENTIALS_PATH", credentialsKeyFile.Name())
471+
} else {
472+
t.Setenv("STACKIT_CREDENTIALS_PATH", "test-path")
473+
}
474+
366475
if test.setToken {
367476
t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "test-token")
368477
} else {
369478
t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "")
370479
}
371-
t.Setenv("STACKIT_CREDENTIALS_PATH", "test-path")
372480
t.Setenv("STACKIT_SERVICE_ACCOUNT_EMAIL", "test-email")
373481

374482
// Get the default authentication client and ensure that it's not nil

0 commit comments

Comments
 (0)