Skip to content

Commit bf11d64

Browse files
committed
fix: add login skip validation and unified docs generation
1 parent 716927e commit bf11d64

147 files changed

Lines changed: 1556 additions & 1196 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
`ServerUsername`, `ServerPassword`, and store password values.
99
- `stores import csv`: Preserve JSON-shaped secret values as secret strings instead of parsing them into nested
1010
request objects.
11+
- `login`: Add `--skip-validate` to save login configuration without validating credentials against Keyfactor Command.
1112

1213
### Docs
1314

@@ -17,6 +18,8 @@
1718
- Add use-case documentation for migrating certificate store credentials from static values to a PAM provider.
1819
- Add generated per-store-type bulk create and update use-case guides.
1920
- Add generated PAM Operations use-case documentation for PAM type and provider creation.
21+
- `makedocs` now regenerates command docs, store-type use cases, and PAM operation use cases without date-based
22+
generated footers.
2023

2124
# v1.9.1
2225

artifacts/pam/pam-create-template.json

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@
2626
"DataType": 2,
2727
"InstanceLevel": false
2828
},
29+
{
30+
"Id": -1,
31+
"Name": "ClientId",
32+
"DisplayName": "Client ID",
33+
"DataType": 2,
34+
"InstanceLevel": false
35+
},
36+
{
37+
"Id": -1,
38+
"Name": "ClientSecret",
39+
"DisplayName": "ClientSecret",
40+
"DataType": 2,
41+
"InstanceLevel": false
42+
},
43+
{
44+
"Id": -1,
45+
"Name": "GrantType",
46+
"DisplayName": "Grant Type",
47+
"DataType": 1,
48+
"InstanceLevel": false
49+
},
2950
{
3051
"Id": -1,
3152
"Name": "SecretId",
@@ -72,6 +93,36 @@
7293
"DataType": 1,
7394
"InstanceLevel": false
7495
}
96+
},
97+
{
98+
"Value": "N/A",
99+
"ProviderTypeParam": {
100+
"Id": -1,
101+
"Name": "ClientId",
102+
"DisplayName": "Client ID",
103+
"DataType": 2,
104+
"InstanceLevel": false
105+
}
106+
},
107+
{
108+
"Value": "N/A",
109+
"ProviderTypeParam": {
110+
"Id": -1,
111+
"Name": "ClientSecret",
112+
"DisplayName": "ClientSecret",
113+
"DataType": 2,
114+
"InstanceLevel": false
115+
}
116+
},
117+
{
118+
"Value": "password",
119+
"ProviderTypeParam": {
120+
"Id": -1,
121+
"Name": "GrantType",
122+
"DisplayName": "Grant Type",
123+
"DataType": 1,
124+
"InstanceLevel": false
125+
}
75126
}
76127
]
77-
}
128+
}

cmd/login.go

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"os"
2121
"path"
22+
"strconv"
2223
"strings"
2324

2425
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
@@ -28,6 +29,8 @@ import (
2829
"golang.org/x/term"
2930
)
3031

32+
var loginSkipValidate bool
33+
3134
var loginCmd = &cobra.Command{
3235
Use: "login",
3336
Aliases: nil,
@@ -78,16 +81,27 @@ WARNING: This will write the environmental credentials to disk and will be store
7881
kfcOAuth *auth_providers.CommandConfigOauth
7982
kfcBasicAuth *auth_providers.CommandAuthConfigBasic
8083
)
84+
skipValidate := loginSkipValidate
8185

8286
log.Debug().Msg("calling getEnvConfig()")
83-
envConfig, envErr := getServerConfigFromEnv()
87+
var envConfig *auth_providers.Server
88+
var envErr error
89+
if skipValidate {
90+
envConfig, envErr = getServerConfigFromEnvNoValidate()
91+
} else {
92+
envConfig, envErr = getServerConfigFromEnv()
93+
}
8494
if envErr == nil {
8595
log.Debug().Msg("getEnvConfig() returned")
96+
message := fmt.Sprintf("Login successful via environment variables to %s", envConfig.Host)
97+
if skipValidate {
98+
message = fmt.Sprintf("Login configuration saved from environment variables to %s; credential validation skipped", envConfig.Host)
99+
}
86100
log.Info().
87101
Str("host", envConfig.Host).
88102
Str("authType", envConfig.AuthType).
89103
Msg("Login successful via environment variables")
90-
outputResult(fmt.Sprintf("Login successful via environment variables to %s", envConfig.Host), outputFormat)
104+
outputResult(message, outputFormat)
91105
if profile == "" {
92106
profile = "default"
93107
}
@@ -227,13 +241,27 @@ WARNING: This will write the environmental credentials to disk and will be store
227241
log.Error().Msg("unable to determine auth type from interactive configuration")
228242
}
229243
}
244+
if !skipValidate {
245+
skipValidate = !promptForInteractiveYesNo("Validate credentials with Keyfactor Command now?")
246+
}
230247
}
231248

232249
if !isValidConfig {
233250
log.Debug().Msg("prompting for interactive login")
234251
return fmt.Errorf("unable to determine valid configuration")
235252
}
236253

254+
if skipValidate {
255+
log.Info().
256+
Str("profile", profile).
257+
Str("configFile", configFile).
258+
Str("host", outputServer.Host).
259+
Str("authType", authType).
260+
Msg("Login configuration saved; credential validation skipped")
261+
outputResult(fmt.Sprintf("Login configuration saved to %s; credential validation skipped", outputServer.Host), outputFormat)
262+
return nil
263+
}
264+
237265
if authType == "oauth" {
238266
log.Debug().
239267
Str("profile", profile).
@@ -297,6 +325,98 @@ WARNING: This will write the environmental credentials to disk and will be store
297325

298326
func init() {
299327
RootCmd.AddCommand(loginCmd)
328+
loginCmd.Flags().BoolVar(
329+
&loginSkipValidate,
330+
"skip-validate",
331+
false,
332+
"Save the login configuration without validating credentials against Keyfactor Command.",
333+
)
334+
}
335+
336+
func getServerConfigFromEnvNoValidate() (*auth_providers.Server, error) {
337+
hostname, hOk := os.LookupEnv(auth_providers.EnvKeyfactorHostName)
338+
if !hOk || hostname == "" {
339+
return nil, fmt.Errorf("environment variable %s is required", auth_providers.EnvKeyfactorHostName)
340+
}
341+
342+
apiPath := os.Getenv(auth_providers.EnvKeyfactorAPIPath)
343+
if apiPath == "" {
344+
apiPath = auth_providers.DefaultCommandAPIPath
345+
}
346+
skipVerify := skipVerifyFromEnv()
347+
348+
username, uOk := os.LookupEnv(auth_providers.EnvKeyfactorUsername)
349+
password, pOk := os.LookupEnv(auth_providers.EnvKeyfactorPassword)
350+
if uOk && pOk {
351+
serverConfig := &auth_providers.Server{
352+
Host: hostname,
353+
APIPath: apiPath,
354+
Username: username,
355+
Password: password,
356+
Domain: os.Getenv(auth_providers.EnvKeyfactorDomain),
357+
SkipTLSVerify: skipVerify,
358+
AuthType: "basic",
359+
}
360+
if _, err := serverConfig.GetBasicAuthClientConfig(); err != nil {
361+
return nil, err
362+
}
363+
return serverConfig, nil
364+
}
365+
366+
clientID, cOk := os.LookupEnv(auth_providers.EnvKeyfactorClientID)
367+
clientSecret, csOk := os.LookupEnv(auth_providers.EnvKeyfactorClientSecret)
368+
tokenURL, tOk := os.LookupEnv(auth_providers.EnvKeyfactorAuthTokenURL)
369+
if cOk && csOk && tOk {
370+
serverConfig := &auth_providers.Server{
371+
Host: hostname,
372+
APIPath: apiPath,
373+
ClientID: clientID,
374+
ClientSecret: clientSecret,
375+
OAuthTokenUrl: tokenURL,
376+
Scopes: authScopesFromCSV(os.Getenv(auth_providers.EnvKeyfactorAuthScopes)),
377+
Audience: os.Getenv(auth_providers.EnvKeyfactorAuthAudience),
378+
SkipTLSVerify: skipVerify,
379+
AuthType: "oauth",
380+
}
381+
if _, err := serverConfig.GetOAuthClientConfig(); err != nil {
382+
return nil, err
383+
}
384+
return serverConfig, nil
385+
}
386+
387+
return nil, fmt.Errorf(
388+
"incomplete environment variable configuration, " +
389+
"please provide basic auth credentials or oAuth credentials",
390+
)
391+
}
392+
393+
func skipVerifyFromEnv() bool {
394+
if skipVerifyFlag {
395+
return true
396+
}
397+
value := strings.ToLower(os.Getenv(auth_providers.EnvKeyfactorSkipVerify))
398+
parsed, err := strconv.ParseBool(value)
399+
if err == nil {
400+
return parsed
401+
}
402+
return value == "yes" || value == "y"
403+
}
404+
405+
func authScopesFromCSV(scopesCSV string) []string {
406+
if scopesCSV == "" {
407+
return auth_providers.DefaultScopes
408+
}
409+
var scopes []string
410+
for _, scope := range strings.Split(scopesCSV, ",") {
411+
scope = strings.TrimSpace(scope)
412+
if scope != "" {
413+
scopes = append(scopes, scope)
414+
}
415+
}
416+
if len(scopes) == 0 {
417+
return auth_providers.DefaultScopes
418+
}
419+
return scopes
300420
}
301421

302422
func writeConfigFile(configFile *auth_providers.Config, configPath string) error {

cmd/login_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ func Test_LoginFileNoPrompt(t *testing.T) {
9696
defer setBasicEnvVariables(username, password, domain)
9797

9898
npfCmd := RootCmd
99-
npfCmd.SetArgs([]string{"login", "--no-prompt"})
99+
npfCmd.SetArgs(
100+
[]string{"login", "--no-prompt", "--skip-validate", "--config", configFilePath, "--profile", "default"},
101+
)
100102

101103
output := captureOutput(
102104
func() {
@@ -108,7 +110,7 @@ func Test_LoginFileNoPrompt(t *testing.T) {
108110
},
109111
)
110112
t.Logf("output: %s", output)
111-
assert.Contains(t, output, "Login successful to")
113+
assert.Contains(t, output, "Login configuration saved")
112114
testConfigExists(t, configFilePath, true)
113115
testConfigValid(t)
114116
//testLogout(t)
@@ -165,7 +167,7 @@ func testLogout(t *testing.T, configFilePath string, restoreConfig bool) {
165167
t.FailNow()
166168
}
167169
}
168-
testCmd.SetArgs([]string{"logout"})
170+
testCmd.SetArgs([]string{"logout", "--no-prompt"})
169171
output := captureOutput(
170172
func() {
171173
err := testCmd.Execute()
@@ -174,7 +176,7 @@ func testLogout(t *testing.T, configFilePath string, restoreConfig bool) {
174176
)
175177
t.Logf("output: %s", output)
176178

177-
assert.Contains(t, output, "Logged out successfully!")
179+
assert.Contains(t, output, "Logged out successfully")
178180

179181
// Test that the config file does not exist
180182
if _, fErr := os.Stat(configFile); !os.IsNotExist(fErr) {

cmd/pam.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ var pamProvidersUpdateCmd = &cobra.Command{
328328

329329
log.Debug().Msg("call: PAMProviderUpdatePamProvider()")
330330
updateRequest := keyfactor.ProviderUpdateRequestLegacy{
331+
Id: pamProvider.Id,
331332
Name: pamProvider.Name,
332333
Remote: pamProvider.Remote,
333334
Area: pamProvider.Area,
@@ -339,8 +340,8 @@ var pamProvidersUpdateCmd = &cobra.Command{
339340
updatedPamProvider, cErr := kfClient.UpdatePAMProvider(&updateRequest)
340341

341342
log.Debug().Msg("returned: PAMProviderUpdatePamProvider()")
342-
if err != nil {
343-
return err
343+
if cErr != nil {
344+
return cErr
344345
}
345346

346347
log.Debug().Msg(convertResponseMsg)

cmd/pam_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -993,8 +993,7 @@ func testListPamProviders(t *testing.T) ([]any, error) {
993993
assert.NotEmpty(t, providerConfig["Id"])
994994
assert.NotEmpty(t, providerConfig["ProviderType"])
995995

996-
pTypeParams := providerConfig["ProviderType"].(map[string]any)["ProviderTypeParams"].([]any)
997-
assert.NotEmpty(t, pTypeParams)
996+
pTypeParams, _ := providerConfig["ProviderType"].(map[string]any)["ProviderTypeParams"].([]any)
998997
assert.GreaterOrEqual(t, len(pTypeParams), 0)
999998
if len(pTypeParams) > 0 {
1000999
for _, param := range pTypeParams {
@@ -1194,10 +1193,14 @@ func testFormatPamCreateConfig(t *testing.T, inputFileName string, providerName
11941193
case map[string]any:
11951194
aProviderType := apiProviderType.(map[string]any)
11961195
cProviderType["Id"] = aProviderType["Id"]
1197-
cProviderType["ProviderTypeParams"] = aProviderType["ProviderTypeParams"]
1196+
apiProviderTypeParams, ok := aProviderType["ProviderTypeParams"]
1197+
if !ok || apiProviderTypeParams == nil {
1198+
apiProviderTypeParams = aProviderType["Parameters"]
1199+
}
1200+
cProviderType["ProviderTypeParams"] = apiProviderTypeParams
11981201
nameToIdMap := make(map[string]int)
11991202
paramsFieldName := "ProviderTypeParams"
1200-
_, ok := cProviderType[paramsFieldName]
1203+
_, ok = cProviderType[paramsFieldName]
12011204
if ok && cProviderType[paramsFieldName] != nil {
12021205
t.Logf("PAM definition is v10 or earlier")
12031206
for _, cParam := range cProviderType[paramsFieldName].([]any) {

0 commit comments

Comments
 (0)