Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f5093bf
Merge pull request #48 from Keyfactor/release-v1.5
indrora Apr 2, 2026
e114c03
Release v1.5 (#49)
spbsoluble May 14, 2026
82090c5
fix(core): preserve HttpClientTimeout in Server via ClientTimeout fie…
spbsoluble Aug 14, 2026
08a4cb4
fix(core): honor config-file ClientTimeout and clamp bad env values t…
spbsoluble Aug 14, 2026
96ef829
fix(auth): redact secrets from logged request bodies and stop scaling…
spbsoluble Aug 14, 2026
bb3f0cf
fix(transport): stop capping MaxConnsPerHost at 10 for cached clients
spbsoluble Aug 14, 2026
155b8e0
fix(core): stop persisting a synthesized default ClientTimeout that s…
spbsoluble Aug 14, 2026
e263b6c
fix(auth): redact secrets nested inside JSON-encoded string values
spbsoluble Aug 14, 2026
2f1d05e
fix(basic-auth): gate persisted ClientTimeout on explicit configuration
spbsoluble Aug 14, 2026
27bd526
fix(kerberos-auth): gate persisted ClientTimeout on explicit configur…
spbsoluble Aug 14, 2026
b2dc695
fix(oauth-auth): gate persisted ClientTimeout on explicit configuration
spbsoluble Aug 14, 2026
343ba66
fix(auth): strip leading BOM before nested-JSON-in-string redaction c…
spbsoluble Aug 14, 2026
2ab3fc7
fix(auth): bound the OAuth token-fetch dial phase and overall timeout
spbsoluble Aug 17, 2026
14ce9fd
fix(auth): bound GetAccessToken's token-fetch call and fix test hostn…
spbsoluble Aug 17, 2026
a619735
fix(auth): bound aggregate OAuth token-fetch time, not just per-attempt
spbsoluble Aug 17, 2026
6564546
fix(core): preserve HttpClientTimeout in Server via ClientTimeout fie…
spbsoluble Aug 27, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v1.3.1
## Fixes
- Reuse OAuth2 token source to prevent unnecessary token fetches for each request.

# v1.3.0

## Features
Expand Down
28 changes: 12 additions & 16 deletions auth_providers/auth_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,22 +241,18 @@ func (a *CommandAuthConfigBasic) parseUsernameDomain() error {

// GetServerConfig returns the server configuration
func (a *CommandAuthConfigBasic) GetServerConfig() *Server {
server := Server{
Host: a.CommandHostName,
Port: a.CommandPort,
Username: a.Username,
Password: a.Password,
Domain: a.Domain,
ClientID: "",
ClientSecret: "",
OAuthTokenUrl: "",
APIPath: a.CommandAPIPath,
//AuthProvider: AuthProvider{},
SkipTLSVerify: a.SkipVerify,
CACertPath: a.CommandCACert,
AuthType: "basic",
}
return &server
// Delegate to the embedded CommandAuthConfig for the fields it already
// knows how to populate correctly -- notably ClientTimeout, which must be
// omitted (not the ValidateAuthConfig-synthesized default) unless the
// caller explicitly configured it. See clientTimeoutDefaulted's doc
// comment on CommandAuthConfig for why persisting a synthesized default
// is harmful. Layer basic-auth-specific fields on top.
server := a.CommandAuthConfig.GetServerConfig()
server.Username = a.Username
server.Password = a.Password
server.Domain = a.Domain
server.AuthType = "basic"
return server
}

// Example usage of CommandAuthConfigBasic
Expand Down
124 changes: 124 additions & 0 deletions auth_providers/auth_basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package auth_providers_test

import (
"encoding/json"
"fmt"
"net/http"
"os"
Expand Down Expand Up @@ -272,6 +273,129 @@ func unsetBasicEnvVariables() {
os.Unsetenv(auth_providers.EnvKeyfactorDomain)
}

// TestCommandAuthConfigBasic_GetServerConfig_DoesNotPersistSynthesizedDefault
// is the CommandAuthConfigBasic analogue of
// TestCommandAuthConfig_GetServerConfig_DoesNotPersistSynthesizedDefault in
// auth_core_test.go. CommandAuthConfigBasic defines its own GetServerConfig()
// that shadows the embedded CommandAuthConfig's method via Go's method
// resolution, so a fix landed only on the base type does not protect this --
// or any other real caller-facing -- concrete type. CommandAuthConfigBasic is
// what every real basic-auth caller (kfutil, keyfactor-go-client, etc.)
// actually constructs.
//
// A value that was never explicitly configured (no struct field, no
// WithClientTimeout(), no env var, no file value) must not be serialized.
func TestCommandAuthConfigBasic_GetServerConfig_DoesNotPersistSynthesizedDefault(t *testing.T) {
t.Setenv(auth_providers.EnvKeyfactorUsername, "test-user")
t.Setenv(auth_providers.EnvKeyfactorPassword, "test-pass")
t.Setenv(auth_providers.EnvKeyfactorDomain, "test-domain")

config := &auth_providers.CommandAuthConfigBasic{
CommandAuthConfig: auth_providers.CommandAuthConfig{
CommandHostName: "test-host",
CommandPort: 443,
CommandAPIPath: "KeyfactorAPI",
},
}

if err := config.ValidateAuthConfig(); err != nil {
t.Fatalf("expected no error, got %v", err)
}

server := config.GetServerConfig()
if server.ClientTimeout != 0 {
t.Fatalf("expected Server.ClientTimeout to be omitted (0) for a synthesized default, got %d", server.ClientTimeout)
}
}

// TestCommandAuthConfigBasic_GetServerConfig_PersistsExplicitTimeout proves
// the companion positive case: an explicitly configured timeout must still be
// serialized by CommandAuthConfigBasic.GetServerConfig().
func TestCommandAuthConfigBasic_GetServerConfig_PersistsExplicitTimeout(t *testing.T) {
t.Setenv(auth_providers.EnvKeyfactorUsername, "test-user")
t.Setenv(auth_providers.EnvKeyfactorPassword, "test-pass")
t.Setenv(auth_providers.EnvKeyfactorDomain, "test-domain")

config := &auth_providers.CommandAuthConfigBasic{
CommandAuthConfig: auth_providers.CommandAuthConfig{
CommandHostName: "test-host",
CommandPort: 443,
CommandAPIPath: "KeyfactorAPI",
},
}
config.WithClientTimeout(300)

if err := config.ValidateAuthConfig(); err != nil {
t.Fatalf("expected no error, got %v", err)
}

server := config.GetServerConfig()
if server.ClientTimeout != 300 {
t.Fatalf("expected Server.ClientTimeout to be 300, got %d", server.ClientTimeout)
}
}

// TestCommandAuthConfigBasic_PersistedDefaultConfigFile_DoesNotShadowEnvVar is
// the CommandAuthConfigBasic analogue of
// TestCommandAuthConfig_PersistedDefaultConfigFile_DoesNotShadowEnvVar: a
// synthesized default persisted to a config file by a first run must not
// shadow KEYFACTOR_CLIENT_TIMEOUT on a second run that loads that file.
func TestCommandAuthConfigBasic_PersistedDefaultConfigFile_DoesNotShadowEnvVar(t *testing.T) {
t.Setenv(auth_providers.EnvKeyfactorUsername, "test-user")
t.Setenv(auth_providers.EnvKeyfactorPassword, "test-pass")
t.Setenv(auth_providers.EnvKeyfactorDomain, "test-domain")

// Run 1: nothing explicitly configured for client timeout.
run1 := &auth_providers.CommandAuthConfigBasic{
CommandAuthConfig: auth_providers.CommandAuthConfig{
CommandHostName: "test-host",
CommandPort: 443,
CommandAPIPath: "KeyfactorAPI",
},
}
if err := run1.ValidateAuthConfig(); err != nil {
t.Fatalf("run1: expected no error, got %v", err)
}

persisted := run1.GetServerConfig()

// Persist exactly what kfutil's login flow persists: the resolved Server
// config, verbatim, to the "default" profile of a config file.
dir := t.TempDir()
path := dir + "/command_config.json"
fileContents, mErr := json.Marshal(
map[string]interface{}{
"servers": map[string]interface{}{
"default": persisted,
},
},
)
if mErr != nil {
t.Fatalf("failed to marshal persisted config: %v", mErr)
}
if err := os.WriteFile(path, fileContents, 0o600); err != nil {
t.Fatalf("failed to write persisted config file: %v", err)
}

// Run 2: a fresh process loads that persisted file and has
// KEYFACTOR_CLIENT_TIMEOUT set in its environment.
t.Setenv(auth_providers.EnvKeyfactorClientTimeout, "1800")

run2 := &auth_providers.CommandAuthConfigBasic{}
run2.WithConfigFile(path).WithConfigProfile("default")

if err := run2.ValidateAuthConfig(); err != nil {
t.Fatalf("run2: expected no error from ValidateAuthConfig, got %v", err)
}

if run2.HttpClientTimeout != 1800 {
t.Fatalf(
"expected KEYFACTOR_CLIENT_TIMEOUT=1800 to be honored, but a persisted synthesized default shadowed it: got HttpClientTimeout=%d",
run2.HttpClientTimeout,
)
}
}

func authBasicTest(
t *testing.T, testName string, allowFail bool, config *auth_providers.CommandAuthConfigBasic,
errorContains ...string,
Expand Down
Loading
Loading