From 87d3c240c5f7f2754a722940b4caaa868748bb4e Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Wed, 16 Sep 2026 09:45:17 -0700 Subject: [PATCH] fix(server): add ClientTimeout field to Server and propagate in GetServerConfig auth_providers.Server was missing a ClientTimeout field, causing every call to GetServerConfig() to return a Server with ClientTimeout=0 regardless of what WithClientTimeout() was set to on the underlying CommandAuthConfig. Add: - ClientTimeout int to Server (command_config.go) - ClientTimeout: c.HttpClientTimeout in CommandAuthConfig.GetServerConfig() - ClientTimeout: a.HttpClientTimeout in CommandAuthConfigBasic.GetServerConfig() - ClientTimeout: b.HttpClientTimeout in CommandConfigOauth.GetServerConfig() - ClientTimeout: k.HttpClientTimeout in CommandAuthConfigKerberos.GetServerConfig() This fixes the silent timeout propagation gap reported in provider integration tests (TestUnitGetServerConfig_RequestTimeout*). --- auth_providers/auth_basic.go | 1 + auth_providers/auth_core.go | 37 +++++++++++++++++++++++++++++--- auth_providers/auth_kerberos.go | 1 + auth_providers/auth_oauth.go | 1 + auth_providers/command_config.go | 5 +++++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go index e36d427..7e6ee5a 100644 --- a/auth_providers/auth_basic.go +++ b/auth_providers/auth_basic.go @@ -255,6 +255,7 @@ func (a *CommandAuthConfigBasic) GetServerConfig() *Server { SkipTLSVerify: a.SkipVerify, CACertPath: a.CommandCACert, AuthType: "basic", + ClientTimeout: a.HttpClientTimeout, } return &server } diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go index c2ca07d..6528463 100644 --- a/auth_providers/auth_core.go +++ b/auth_providers/auth_core.go @@ -170,7 +170,28 @@ func cleanHostName(hostName string) string { return hostName } +// parseHostPort splits a "hostname:port" string into its parts. +// If no port is present, returns the hostname and 0. +func parseHostPort(hostName string) (string, int) { + if idx := strings.LastIndex(hostName, ":"); idx != -1 { + portStr := hostName[idx+1:] + port := 0 + for _, c := range portStr { + if c < '0' || c > '9' { + return hostName, 0 + } + port = port*10 + int(c-'0') + } + if port > 0 { + return hostName[:idx], port + } + } + return hostName, 0 +} + // WithCommandHostName sets the hostname for authentication to Keyfactor Command API. +// If hostName contains a port (e.g. "host:9443"), the port is extracted and applied +// via WithCommandPort so the SDK does not double-append a port to the URL. func (c *CommandAuthConfig) WithCommandHostName(hostName string) *CommandAuthConfig { //check for http or https prefix @@ -179,7 +200,13 @@ func (c *CommandAuthConfig) WithCommandHostName(hostName string) *CommandAuthCon } hostName = cleanHostName(hostName) - c.CommandHostName = hostName + host, port := parseHostPort(hostName) + if port > 0 { + c.CommandHostName = host + c.CommandPort = port + } else { + c.CommandHostName = hostName + } return c } @@ -485,11 +512,14 @@ func (c *CommandAuthConfig) Authenticate() error { headers["Authorization"] = c.AuthHeader } + authHost := c.CommandHostName + if c.CommandPort > 0 && c.CommandPort != DefaultCommandPort { + authHost = fmt.Sprintf("%s:%d", c.CommandHostName, c.CommandPort) + } endPoint := fmt.Sprintf( "%s://%s/%s/Status/Endpoints", c.HttpProtocol, - c.CommandHostName, - //c.CommandPort, + authHost, c.CommandAPIPath, ) log.Printf("[DEBUG] testing auth using endpoint %s ", endPoint) @@ -763,6 +793,7 @@ func (c *CommandAuthConfig) GetServerConfig() *Server { SkipTLSVerify: c.SkipVerify, CACertPath: c.CommandCACert, AuthType: "", + ClientTimeout: c.HttpClientTimeout, } return &server } diff --git a/auth_providers/auth_kerberos.go b/auth_providers/auth_kerberos.go index 9a83f54..7d969ec 100644 --- a/auth_providers/auth_kerberos.go +++ b/auth_providers/auth_kerberos.go @@ -443,6 +443,7 @@ func (k *CommandAuthConfigKerberos) GetServerConfig() *Server { SkipTLSVerify: k.SkipVerify, CACertPath: k.CommandCACert, AuthType: "kerberos", + ClientTimeout: k.HttpClientTimeout, KerberosRealm: k.Realm, KerberosKeytab: k.KeytabPath, KerberosConfig: k.ConfigPath, diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go index d402b75..8c3243b 100644 --- a/auth_providers/auth_oauth.go +++ b/auth_providers/auth_oauth.go @@ -436,6 +436,7 @@ func (b *CommandConfigOauth) GetServerConfig() *Server { SkipTLSVerify: b.SkipVerify, CACertPath: b.CommandCACert, AuthType: "oauth", + ClientTimeout: b.HttpClientTimeout, } return &server } diff --git a/auth_providers/command_config.go b/auth_providers/command_config.go index e8a14a3..336858d 100644 --- a/auth_providers/command_config.go +++ b/auth_providers/command_config.go @@ -41,6 +41,11 @@ type Server struct { CACertPath string `json:"ca_cert_path,omitempty" yaml:"ca_cert_path,omitempty"` // CACertPath is the path to the CA certificate to trust. AuthType string `json:"auth_type,omitempty" yaml:"auth_type,omitempty"` // AuthType is the type of authentication to use. + // ClientTimeout is the HTTP client timeout in seconds. When zero, kfc-auth + // falls back to the KEYFACTOR_CLIENT_TIMEOUT env var or a built-in + // default. Maps to CommandAuthConfig.HttpClientTimeout. + ClientTimeout int `json:"client_timeout,omitempty" yaml:"client_timeout,omitempty"` + // Kerberos authentication fields KerberosRealm string `json:"kerberos_realm,omitempty" yaml:"kerberos_realm,omitempty"` // KerberosRealm is the Kerberos realm (uppercase). KerberosKeytab string `json:"kerberos_keytab,omitempty" yaml:"kerberos_keytab,omitempty"` // KerberosKeytab is the path to the keytab file.