diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml index bcb842a..9ce385a 100644 --- a/.github/workflows/go_tests.yml +++ b/.github/workflows/go_tests.yml @@ -11,8 +11,6 @@ jobs: strategy: matrix: environment: -# - "KFC_10_5_0" -# - "KFC_12_3_0_KC" - "ses_2441" environment: ${{ matrix.environment }} steps: diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index 64a6352..bd5f384 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -3,7 +3,7 @@ name: Keyfactor Bootstrap Workflow on: workflow_dispatch: pull_request: - types: [ opened, closed, synchronize, edited, reopened ] + types: [opened, closed, synchronize, edited, reopened] push: create: branches: @@ -11,10 +11,17 @@ on: jobs: call-starter-workflow: - uses: keyfactor/actions/.github/workflows/starter.yml@v3 + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + with: + command_token_url: ${{ vars.COMMAND_TOKEN_URL }} + command_hostname: ${{ vars.COMMAND_HOSTNAME }} + command_base_api_path: ${{ vars.COMMAND_API_PATH }} secrets: token: ${{ secrets.V2BUILDTOKEN}} - APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} - scan_token: ${{ secrets.SAST_TOKEN }} \ No newline at end of file + scan_token: ${{ secrets.SAST_TOKEN }} + entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} + entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} + command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} + command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} \ No newline at end of file diff --git a/README.md b/README.md index a0f72e2..1e783aa 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,30 @@ Currently `Basic Authentication` via `Active Directory` is the *ONLY* supported | KEYFACTOR_AUTH_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | | | KEYFACTOR_AUTH_CA_CERT | Either a file path or PEM encoded string to a CA certificate to use when connecting to Keyfactor Auth | | +### Kerberos/SPNEGO Authentication + +Kerberos authentication supports three methods: credential cache (ccache), keytab file, or username/password. The authentication method is determined automatically based on which credentials are provided, with the following priority: ccache > keytab > password. + +| Name | Description | Default | +|-----------------------------------|----------------------------------------------------------------------------------------------------|-------------------| +| KEYFACTOR_AUTH_KRB_USERNAME | Kerberos principal (username or user@REALM format) | | +| KEYFACTOR_AUTH_KRB_PASSWORD | Password for password-based Kerberos authentication | | +| KEYFACTOR_AUTH_KRB_REALM | Kerberos realm (uppercase, e.g., EXAMPLE.COM). Can be implied from username if using user@REALM | | +| KEYFACTOR_AUTH_KRB_KEYTAB | Path to keytab file for keytab-based authentication | | +| KEYFACTOR_AUTH_KRB_CONFIG | Path to krb5.conf file | `/etc/krb5.conf` | +| KEYFACTOR_AUTH_KRB_CCACHE | Path to credential cache file for ccache-based authentication | | +| KEYFACTOR_AUTH_KRB_SPN | Service Principal Name (optional, auto-generated as HTTP/hostname if not specified) | | +| KEYFACTOR_AUTH_KRB_DISABLE_PAFXFAST | Set to `true` to disable PA-FX-FAST for Active Directory compatibility | `false` | + ### Test Environment Variables These environment variables are used to run go tests. They are not used in the actual client library. -| Name | Description | Default | -|------------------------|-------------------------------------------------------|---------| -| TEST_KEYFACTOR_AD_AUTH | Set to `true` to test Active Directory authentication | false | -| TEST_KEYFACTOR_KC_AUTH | Set to `true` to test Keycloak authentication | false | +| Name | Description | Default | +|-------------------------|-------------------------------------------------------|---------| +| TEST_KEYFACTOR_AD_AUTH | Set to `true` to test Active Directory authentication | false | +| TEST_KEYFACTOR_KC_AUTH | Set to `true` to test Keycloak authentication | false | +| TEST_KEYFACTOR_KRB_AUTH | Set to `true` to test Kerberos authentication | false | ## Configuration File @@ -153,6 +169,55 @@ servers: api_path: KeyfactorAPI ``` +### Kerberos/SPNEGO + +#### JSON (with keytab) + +```json +{ + "servers": { + "default": { + "host": "keyfactor.command.kfdelivery.com", + "username": "svc_keyfactor", + "kerberos_realm": "EXAMPLE.COM", + "kerberos_keytab": "/etc/keytabs/svc_keyfactor.keytab", + "kerberos_config": "/etc/krb5.conf", + "api_path": "KeyfactorAPI" + } + } +} +``` + +#### JSON (with password) + +```json +{ + "servers": { + "default": { + "host": "keyfactor.command.kfdelivery.com", + "username": "user@EXAMPLE.COM", + "password": "password", + "kerberos_realm": "EXAMPLE.COM", + "kerberos_config": "/etc/krb5.conf", + "api_path": "KeyfactorAPI" + } + } +} +``` + +#### YAML (with keytab) + +```yaml +servers: + default: + host: keyfactor.command.kfdelivery.com + username: svc_keyfactor + kerberos_realm: EXAMPLE.COM + kerberos_keytab: /etc/keytabs/svc_keyfactor.keytab + kerberos_config: /etc/krb5.conf + api_path: KeyfactorAPI +``` + ## Configuration File Providers Below are a list of configuration file providers that can be used to load configuration from a file if loading from disk @@ -205,4 +270,128 @@ servers: parameters: secret_name: vault_name: +``` + +# Testing + +To run the tests you'll need to provide a `${HOME}/.keyfactor/command_config.json` file for some of the tests to use. + +## Example: + +```json +{ + "servers": { + "default": { + "host": "", + "port": 443, + "client_id": "", + "client_secret": "", + "token_url": "https:///oauth2/token", + "api_path": "Keyfactor/API", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "oauth" + }, + "basic-auth": { + "host": "", + "port": 443, + "username": "", + "password": "", + "domain": "", + "api_path": "KeyfactorAPI", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "basic" + }, + "default": { + "host": "", + "port": 443, + "username": "", + "password": "", + "domain": "", + "api_path": "KeyfactorAPI", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "basic" + }, + "invalid-host": { + "host": "", + "port": 443, + "username": "", + "password": "", + "domain": "", + "api_path": "KeyfactorAPI", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "basic" + }, + "invalid-username": { + "host": "", + "port": 443, + "username": "invalid", + "password": "", + "domain": "", + "api_path": "KeyfactorAPI", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "basic" + }, + "invalid-password": { + "host": "", + "port": 443, + "username": "", + "password": "invalid", + "domain": "", + "api_path": "KeyfactorAPI", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "basic" + }, + "oauth": { + "host": "", + "port": 443, + "client_id": "", + "client_secret": "", + "token_url": "https:///oauth2/token", + "api_path": "Keyfactor/API", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "oauth" + }, + "oauth-invalid-creds": { + "host": "", + "port": 443, + "client_id": "invalid", + "client_secret": "invalid", + "token_url": "https:///oauth2/token", + "api_path": "Keyfactor/API", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "oauth" + }, + "oauth-invalid-host": { + "host": "invalid.localhost.dev", + "port": 443, + "client_id": "", + "client_secret": "", + "token_url": "https:///oauth2/token", + "api_path": "Keyfactor/API", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "oauth" + }, + "oauth-skiptls": { + "host": "", + "port": 443, + "client_id": "", + "client_secret": "", + "token_url": "https:///oauth2/token", + "api_path": "Keyfactor/API", + "auth_provider": {}, + "skip_tls_verify": true, + "auth_type": "oauth" + } + } +} + ``` \ No newline at end of file diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go index 5aa3c50..78f322f 100644 --- a/auth_providers/auth_basic_test.go +++ b/auth_providers/auth_basic_test.go @@ -209,12 +209,12 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) { t.Log("Testing Basic Auth with invalid creds implicit config file") invProfileCreds := &auth_providers.CommandAuthConfigBasic{} - invProfileCreds.WithConfigProfile("invalid_username") + invProfileCreds.WithConfigProfile("invalid-username") authBasicTest(t, "with invalid creds implicit config file", true, invProfileCreds, invalidCredsExpectedError...) t.Log("Testing Basic Auth with invalid Command host implicit config file") invHostConfig := &auth_providers.CommandAuthConfigBasic{} - invHostConfig.WithConfigProfile("invalid_host") + invHostConfig.WithConfigProfile("invalid-host") invHostExpectedError := []string{"no such host"} authBasicTest( t, "with invalid Command host implicit config file", true, invHostConfig, diff --git a/auth_providers/auth_kerberos.go b/auth_providers/auth_kerberos.go new file mode 100644 index 0000000..9a83f54 --- /dev/null +++ b/auth_providers/auth_kerberos.go @@ -0,0 +1,459 @@ +// Copyright 2026 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth_providers + +import ( + "fmt" + "net/http" + "os" + "strings" + + "github.com/jcmturner/gokrb5/v8/client" + "github.com/jcmturner/gokrb5/v8/config" + "github.com/jcmturner/gokrb5/v8/credentials" + "github.com/jcmturner/gokrb5/v8/keytab" + "github.com/jcmturner/gokrb5/v8/spnego" +) + +const ( + // EnvKeyfactorKrbUsername is the environment variable for the Kerberos principal + EnvKeyfactorKrbUsername = "KEYFACTOR_AUTH_KRB_USERNAME" + + // EnvKeyfactorKrbPassword is the environment variable for the Kerberos password + EnvKeyfactorKrbPassword = "KEYFACTOR_AUTH_KRB_PASSWORD" + + // EnvKeyfactorKrbRealm is the environment variable for the Kerberos realm + EnvKeyfactorKrbRealm = "KEYFACTOR_AUTH_KRB_REALM" + + // EnvKeyfactorKrbKeytab is the environment variable for the Kerberos keytab file path + EnvKeyfactorKrbKeytab = "KEYFACTOR_AUTH_KRB_KEYTAB" + + // EnvKeyfactorKrbConfig is the environment variable for the krb5.conf file path + EnvKeyfactorKrbConfig = "KEYFACTOR_AUTH_KRB_CONFIG" + + // EnvKeyfactorKrbCCache is the environment variable for the Kerberos credential cache path + EnvKeyfactorKrbCCache = "KEYFACTOR_AUTH_KRB_CCACHE" + + // EnvKeyfactorKrbSPN is the environment variable for the Service Principal Name + EnvKeyfactorKrbSPN = "KEYFACTOR_AUTH_KRB_SPN" + + // EnvKeyfactorKrbDisablePAFXFast is the environment variable to disable PA-FX-FAST for AD compatibility + EnvKeyfactorKrbDisablePAFXFast = "KEYFACTOR_AUTH_KRB_DISABLE_PAFXFAST" + + // DefaultKrbConfigPath is the default path to krb5.conf + DefaultKrbConfigPath = "/etc/krb5.conf" +) + +// Kerberos Authenticator +var _ Authenticator = &KerberosAuthenticator{} + +// KerberosAuthenticator is an Authenticator that uses Kerberos/SPNEGO for authentication. +type KerberosAuthenticator struct { + Client *http.Client +} + +// GetHttpClient returns the http client +func (k *KerberosAuthenticator) GetHttpClient() (*http.Client, error) { + return k.Client, nil +} + +// CommandAuthConfigKerberos represents the configuration needed for Kerberos authentication to Keyfactor Command API. +type CommandAuthConfigKerberos struct { + // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API + CommandAuthConfig + + // Username is the Kerberos principal (user@REALM or just username) + Username string `json:"username,omitempty" yaml:"username,omitempty"` + + // Password is the password for password-based Kerberos authentication + Password string `json:"password,omitempty" yaml:"password,omitempty"` + + // Realm is the Kerberos realm (uppercase, e.g., EXAMPLE.COM) + Realm string `json:"kerberos_realm,omitempty" yaml:"kerberos_realm,omitempty"` + + // KeytabPath is the path to the keytab file for keytab-based authentication + KeytabPath string `json:"kerberos_keytab,omitempty" yaml:"kerberos_keytab,omitempty"` + + // ConfigPath is the path to krb5.conf (default: /etc/krb5.conf) + ConfigPath string `json:"kerberos_config,omitempty" yaml:"kerberos_config,omitempty"` + + // CCachePath is the path to the Kerberos credential cache + CCachePath string `json:"kerberos_ccache,omitempty" yaml:"kerberos_ccache,omitempty"` + + // SPN is the Service Principal Name (optional, auto-generated from host as HTTP/hostname) + SPN string `json:"kerberos_spn,omitempty" yaml:"kerberos_spn,omitempty"` + + // DisablePAFXFast disables PA-FX-FAST for Active Directory compatibility + DisablePAFXFast bool `json:"kerberos_disable_pafxfast,omitempty" yaml:"kerberos_disable_pafxfast,omitempty"` +} + +// NewKerberosAuthenticatorBuilder creates a new instance of CommandAuthConfigKerberos +func NewKerberosAuthenticatorBuilder() *CommandAuthConfigKerberos { + return &CommandAuthConfigKerberos{} +} + +// WithUsername sets the Kerberos principal for authentication +func (k *CommandAuthConfigKerberos) WithUsername(username string) *CommandAuthConfigKerberos { + k.Username = username + return k +} + +// WithPassword sets the password for password-based Kerberos authentication +func (k *CommandAuthConfigKerberos) WithPassword(password string) *CommandAuthConfigKerberos { + k.Password = password + return k +} + +// WithRealm sets the Kerberos realm +func (k *CommandAuthConfigKerberos) WithRealm(realm string) *CommandAuthConfigKerberos { + k.Realm = strings.ToUpper(realm) + return k +} + +// WithKeytabPath sets the keytab file path for keytab-based authentication +func (k *CommandAuthConfigKerberos) WithKeytabPath(keytabPath string) *CommandAuthConfigKerberos { + k.KeytabPath = keytabPath + return k +} + +// WithConfigPath sets the krb5.conf file path +func (k *CommandAuthConfigKerberos) WithConfigPath(configPath string) *CommandAuthConfigKerberos { + k.ConfigPath = configPath + return k +} + +// WithCCachePath sets the credential cache path for ccache-based authentication +func (k *CommandAuthConfigKerberos) WithCCachePath(ccachePath string) *CommandAuthConfigKerberos { + k.CCachePath = ccachePath + return k +} + +// WithSPN sets the Service Principal Name +func (k *CommandAuthConfigKerberos) WithSPN(spn string) *CommandAuthConfigKerberos { + k.SPN = spn + return k +} + +// WithDisablePAFXFast sets whether to disable PA-FX-FAST for AD compatibility +func (k *CommandAuthConfigKerberos) WithDisablePAFXFast(disable bool) *CommandAuthConfigKerberos { + k.DisablePAFXFast = disable + return k +} + +// spnegoTransport wraps an http.RoundTripper to add SPNEGO/Kerberos authentication +type spnegoTransport struct { + base http.RoundTripper + krbClient *client.Client + spn string +} + +// RoundTrip implements http.RoundTripper and adds SPNEGO authentication headers +func (t *spnegoTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Determine SPN - if not provided, generate from request host + spnToUse := t.spn + if spnToUse == "" { + spnToUse = "HTTP/" + req.URL.Hostname() + } + + // Set the SPNEGO header on the request + if err := spnego.SetSPNEGOHeader(t.krbClient, req, spnToUse); err != nil { + return nil, fmt.Errorf("failed to set SPNEGO header: %w", err) + } + + // Forward the request to the actual transport + return t.base.RoundTrip(req) +} + +// GetHttpClient returns the http client configured with Kerberos/SPNEGO authentication +func (k *CommandAuthConfigKerberos) GetHttpClient() (*http.Client, error) { + // Validate the configuration + cErr := k.ValidateAuthConfig() + if cErr != nil { + return nil, cErr + } + + // Load krb5.conf + cfg, err := config.Load(k.ConfigPath) + if err != nil { + return nil, fmt.Errorf("failed to load Kerberos config from %s: %w", k.ConfigPath, err) + } + + // Create Kerberos client based on authentication method + var krbClient *client.Client + + // Settings for AD compatibility + var settings []func(*client.Settings) + if k.DisablePAFXFast { + settings = append(settings, client.DisablePAFXFAST(true)) + } + + switch { + case k.CCachePath != "": + // Use credential cache + cc, ccErr := credentials.LoadCCache(k.CCachePath) + if ccErr != nil { + return nil, fmt.Errorf("failed to load credential cache from %s: %w", k.CCachePath, ccErr) + } + krbClient, err = client.NewFromCCache(cc, cfg, settings...) + if err != nil { + return nil, fmt.Errorf("failed to create Kerberos client from credential cache: %w", err) + } + + case k.KeytabPath != "": + // Use keytab file + kt, ktErr := keytab.Load(k.KeytabPath) + if ktErr != nil { + return nil, fmt.Errorf("failed to load keytab from %s: %w", k.KeytabPath, ktErr) + } + krbClient = client.NewWithKeytab(k.Username, k.Realm, kt, cfg, settings...) + + default: + // Use password authentication + krbClient = client.NewWithPassword(k.Username, k.Realm, k.Password, cfg, settings...) + } + + // Login to get TGT (not needed for ccache) + if k.CCachePath == "" { + loginErr := krbClient.Login() + if loginErr != nil { + return nil, fmt.Errorf("failed to login to Kerberos: %w", loginErr) + } + } + + // Build base transport with TLS config + transport, tErr := k.CommandAuthConfig.BuildTransport() + if tErr != nil { + return nil, tErr + } + + // Wrap transport with SPNEGO authentication + return &http.Client{ + Transport: &spnegoTransport{ + base: transport, + krbClient: krbClient, + spn: k.SPN, + }, + }, nil +} + +// Build creates a new instance of KerberosAuthenticator +func (k *CommandAuthConfigKerberos) Build() (Authenticator, error) { + client, cErr := k.GetHttpClient() + if cErr != nil { + return nil, cErr + } + k.HttpClient = client + + return &KerberosAuthenticator{Client: client}, nil +} + +// ValidateAuthConfig validates the Kerberos authentication configuration. +func (k *CommandAuthConfigKerberos) ValidateAuthConfig() error { + silentLoad := true + if k.CommandAuthConfig.ConfigProfile != "" { + silentLoad = false + } else if k.CommandAuthConfig.ConfigFilePath != "" { + silentLoad = false + } + serverConfig, cErr := k.CommandAuthConfig.LoadConfig( + k.CommandAuthConfig.ConfigProfile, + k.CommandAuthConfig.ConfigFilePath, + silentLoad, + ) + if !silentLoad && cErr != nil { + return cErr + } + + // Load ConfigPath (krb5.conf) + if k.ConfigPath == "" { + if configPath, ok := os.LookupEnv(EnvKeyfactorKrbConfig); ok { + k.ConfigPath = configPath + } else if serverConfig != nil && serverConfig.KerberosConfig != "" { + k.ConfigPath = serverConfig.KerberosConfig + } else { + k.ConfigPath = DefaultKrbConfigPath + } + } + + // Check if krb5.conf exists + if _, err := os.Stat(k.ConfigPath); os.IsNotExist(err) { + return fmt.Errorf("Kerberos config file not found at %s", k.ConfigPath) + } + + // Load CCachePath + if k.CCachePath == "" { + if ccachePath, ok := os.LookupEnv(EnvKeyfactorKrbCCache); ok { + k.CCachePath = ccachePath + } else if serverConfig != nil && serverConfig.KerberosCCache != "" { + k.CCachePath = serverConfig.KerberosCCache + } + } + + // Load KeytabPath + if k.KeytabPath == "" { + if keytabPath, ok := os.LookupEnv(EnvKeyfactorKrbKeytab); ok { + k.KeytabPath = keytabPath + } else if serverConfig != nil && serverConfig.KerberosKeytab != "" { + k.KeytabPath = serverConfig.KerberosKeytab + } + } + + // Validate authentication method - need at least one of: ccache, keytab, or username/password + hasCCache := k.CCachePath != "" && fileExists(k.CCachePath) + hasKeytab := k.KeytabPath != "" && fileExists(k.KeytabPath) + + if !hasCCache && !hasKeytab { + // Need username and password for password auth + if k.Username == "" { + if username, ok := os.LookupEnv(EnvKeyfactorKrbUsername); ok { + k.Username = username + } else if serverConfig != nil && serverConfig.Username != "" { + k.Username = serverConfig.Username + } else { + return fmt.Errorf( + "Kerberos authentication requires one of: credential cache (%s), keytab (%s), or username (%s)", + EnvKeyfactorKrbCCache, EnvKeyfactorKrbKeytab, EnvKeyfactorKrbUsername, + ) + } + } + + if k.Password == "" { + if password, ok := os.LookupEnv(EnvKeyfactorKrbPassword); ok { + k.Password = password + } else if serverConfig != nil && serverConfig.Password != "" { + k.Password = serverConfig.Password + } else { + return fmt.Errorf( + "password or environment variable %s is required for password-based Kerberos authentication", + EnvKeyfactorKrbPassword, + ) + } + } + } + + // If using keytab, we need username + if hasKeytab && k.Username == "" { + if username, ok := os.LookupEnv(EnvKeyfactorKrbUsername); ok { + k.Username = username + } else if serverConfig != nil && serverConfig.Username != "" { + k.Username = serverConfig.Username + } else { + return fmt.Errorf( + "username or environment variable %s is required for keytab-based Kerberos authentication", + EnvKeyfactorKrbUsername, + ) + } + } + + // Parse realm from username if included (user@REALM format) + k.parseUsernameRealm() + + // Load Realm + if k.Realm == "" { + if realm, ok := os.LookupEnv(EnvKeyfactorKrbRealm); ok { + k.Realm = strings.ToUpper(realm) + } else if serverConfig != nil && serverConfig.KerberosRealm != "" { + k.Realm = strings.ToUpper(serverConfig.KerberosRealm) + } + } + + // Realm is required for keytab and password auth (not for ccache) + if !hasCCache && k.Realm == "" { + return fmt.Errorf("Kerberos realm or environment variable %s is required", EnvKeyfactorKrbRealm) + } + + // Load SPN + if k.SPN == "" { + if spn, ok := os.LookupEnv(EnvKeyfactorKrbSPN); ok { + k.SPN = spn + } else if serverConfig != nil && serverConfig.KerberosSPN != "" { + k.SPN = serverConfig.KerberosSPN + } + // SPN is optional - spnego.NewClient will auto-generate from host if empty + } + + // Load DisablePAFXFast + if !k.DisablePAFXFast { + if disable, ok := os.LookupEnv(EnvKeyfactorKrbDisablePAFXFast); ok { + k.DisablePAFXFast = disable == "true" || disable == "1" + } + } + + return k.CommandAuthConfig.ValidateAuthConfig() +} + +// Authenticate authenticates the request using Kerberos/SPNEGO authentication. +func (k *CommandAuthConfigKerberos) Authenticate() error { + cErr := k.ValidateAuthConfig() + if cErr != nil { + return cErr + } + + // Create Kerberos Client + authy, err := k.Build() + if err != nil { + return err + } + + if authy != nil { + kClient, kerr := authy.GetHttpClient() + if kerr != nil { + return kerr + } + k.SetClient(kClient) + } + + return k.CommandAuthConfig.Authenticate() +} + +// parseUsernameRealm parses the username to extract the realm if it's included in the username. +// It supports the format: "username@REALM" +func (k *CommandAuthConfigKerberos) parseUsernameRealm() { + if strings.Contains(k.Username, "@") { + parts := strings.Split(k.Username, "@") + if len(parts) == 2 { + k.Username = parts[0] + if k.Realm == "" { + k.Realm = strings.ToUpper(parts[1]) + } + } + } +} + +// GetServerConfig returns the server configuration +func (k *CommandAuthConfigKerberos) GetServerConfig() *Server { + server := Server{ + Host: k.CommandHostName, + Port: k.CommandPort, + Username: k.Username, + Password: k.Password, + APIPath: k.CommandAPIPath, + SkipTLSVerify: k.SkipVerify, + CACertPath: k.CommandCACert, + AuthType: "kerberos", + KerberosRealm: k.Realm, + KerberosKeytab: k.KeytabPath, + KerberosConfig: k.ConfigPath, + KerberosCCache: k.CCachePath, + KerberosSPN: k.SPN, + } + return &server +} + +// fileExists checks if a file exists at the given path +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} diff --git a/auth_providers/auth_kerberos_test.go b/auth_providers/auth_kerberos_test.go new file mode 100644 index 0000000..660a98d --- /dev/null +++ b/auth_providers/auth_kerberos_test.go @@ -0,0 +1,316 @@ +// Copyright 2026 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth_providers_test + +import ( + "fmt" + "net/http" + "os" + "strings" + "testing" + + "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" +) + +func TestKerberosAuthenticator_GetHttpClient(t *testing.T) { + // Skip test if TEST_KEYFACTOR_KRB_AUTH is not set + if os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "1" && os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "true" { + t.Skip("Skipping TestKerberosAuthenticator_GetHttpClient - set TEST_KEYFACTOR_KRB_AUTH=true to run") + return + } + + auth := &auth_providers.KerberosAuthenticator{ + Client: &http.Client{}, + } + + client, err := auth.GetHttpClient() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if client == nil { + t.Fatalf("expected a non-nil http.Client") + } +} + +func TestCommandAuthConfigKerberos_ValidateAuthConfig(t *testing.T) { + // Skip test if TEST_KEYFACTOR_KRB_AUTH is not set + if os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "1" && os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "true" { + t.Skip("Skipping TestCommandAuthConfigKerberos_ValidateAuthConfig - set TEST_KEYFACTOR_KRB_AUTH=true to run") + return + } + + config := &auth_providers.CommandAuthConfigKerberos{ + Username: os.Getenv(auth_providers.EnvKeyfactorKrbUsername), + Password: os.Getenv(auth_providers.EnvKeyfactorKrbPassword), + Realm: os.Getenv(auth_providers.EnvKeyfactorKrbRealm), + ConfigPath: os.Getenv(auth_providers.EnvKeyfactorKrbConfig), + } + + err := config.ValidateAuthConfig() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestCommandAuthConfigKerberos_WithBuilderMethods(t *testing.T) { + // This test doesn't require Kerberos infrastructure + config := auth_providers.NewKerberosAuthenticatorBuilder(). + WithUsername("testuser"). + WithPassword("testpass"). + WithRealm("TEST.REALM"). + WithConfigPath("/etc/krb5.conf"). + WithKeytabPath("/path/to/keytab"). + WithCCachePath("/tmp/krb5cc_1000"). + WithSPN("HTTP/server.example.com"). + WithDisablePAFXFast(true) + + if config == nil { + t.Fatalf("expected a non-nil config") + } + + // Verify builder methods work correctly (access via GetServerConfig) + serverConfig := config.GetServerConfig() + + if serverConfig.Username != "testuser" { + t.Errorf("expected username 'testuser', got '%s'", serverConfig.Username) + } + if serverConfig.Password != "testpass" { + t.Errorf("expected password 'testpass', got '%s'", serverConfig.Password) + } + if serverConfig.KerberosRealm != "TEST.REALM" { + t.Errorf("expected realm 'TEST.REALM', got '%s'", serverConfig.KerberosRealm) + } + if serverConfig.KerberosConfig != "/etc/krb5.conf" { + t.Errorf("expected config path '/etc/krb5.conf', got '%s'", serverConfig.KerberosConfig) + } + if serverConfig.KerberosKeytab != "/path/to/keytab" { + t.Errorf("expected keytab path '/path/to/keytab', got '%s'", serverConfig.KerberosKeytab) + } + if serverConfig.KerberosCCache != "/tmp/krb5cc_1000" { + t.Errorf("expected ccache path '/tmp/krb5cc_1000', got '%s'", serverConfig.KerberosCCache) + } + if serverConfig.KerberosSPN != "HTTP/server.example.com" { + t.Errorf("expected SPN 'HTTP/server.example.com', got '%s'", serverConfig.KerberosSPN) + } + if serverConfig.AuthType != "kerberos" { + t.Errorf("expected auth type 'kerberos', got '%s'", serverConfig.AuthType) + } +} + +func TestCommandAuthConfigKerberos_RealmNormalization(t *testing.T) { + // Test that realm is normalized to uppercase + config := auth_providers.NewKerberosAuthenticatorBuilder(). + WithRealm("example.com") + + serverConfig := config.GetServerConfig() + if serverConfig.KerberosRealm != "EXAMPLE.COM" { + t.Errorf("expected realm to be uppercase 'EXAMPLE.COM', got '%s'", serverConfig.KerberosRealm) + } +} + +func TestCommandAuthConfigKerberos_GetHttpClient(t *testing.T) { + // Skip test if TEST_KEYFACTOR_KRB_AUTH is not set + if os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "1" && os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "true" { + t.Skip("Skipping TestCommandAuthConfigKerberos_GetHttpClient - set TEST_KEYFACTOR_KRB_AUTH=true to run") + return + } + + config := &auth_providers.CommandAuthConfigKerberos{ + Username: os.Getenv(auth_providers.EnvKeyfactorKrbUsername), + Password: os.Getenv(auth_providers.EnvKeyfactorKrbPassword), + Realm: os.Getenv(auth_providers.EnvKeyfactorKrbRealm), + ConfigPath: os.Getenv(auth_providers.EnvKeyfactorKrbConfig), + } + + client, err := config.GetHttpClient() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if client == nil { + t.Fatalf("expected a non-nil http.Client") + } +} + +func TestCommandAuthConfigKerberos_Authenticate(t *testing.T) { + // Skip test if TEST_KEYFACTOR_KRB_AUTH is not set + if os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "1" && os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "true" { + t.Skip("Skipping TestCommandAuthConfigKerberos_Authenticate - set TEST_KEYFACTOR_KRB_AUTH=true to run") + return + } + + t.Log("Testing Kerberos Auth with Environmental variables") + noParamsConfig := &auth_providers.CommandAuthConfigKerberos{} + authKerberosTest(t, "with complete Environmental variables", false, noParamsConfig) + + t.Log("Testing Kerberos Auth with invalid config file path") + invFilePath := &auth_providers.CommandAuthConfigKerberos{} + invFilePath.WithConfigFile("invalid-file-path") + invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"} + authKerberosTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...) + + // Environment variables are not set + t.Log("Unsetting environment variables") + username, password, realm, keytab, configPath, ccache := exportKerberosEnvVariables() + unsetKerberosEnvVariables() + defer func() { + t.Log("Resetting environment variables") + setKerberosEnvVariables(username, password, realm, keytab, configPath, ccache) + }() + + t.Log("Testing Kerberos Auth with no Environmental variables") + incompleteEnvConfig := &auth_providers.CommandAuthConfigKerberos{} + incompleteEnvConfigExpectedError := "Kerberos authentication requires one of" + authKerberosTest( + t, + "with incomplete Environmental variables", + true, + incompleteEnvConfig, + incompleteEnvConfigExpectedError, + ) + + t.Log("Testing auth with only username") + usernameOnlyConfig := &auth_providers.CommandAuthConfigKerberos{ + Username: "test-username", + } + usernameOnlyConfigExpectedError := "password or environment variable" + authKerberosTest(t, "username only", true, usernameOnlyConfig, usernameOnlyConfigExpectedError) + + t.Log("Testing auth with username and password but no realm") + noRealmConfig := &auth_providers.CommandAuthConfigKerberos{ + Username: "test-username", + Password: "test-password", + } + noRealmExpectedError := "Kerberos realm or environment variable" + authKerberosTest(t, "no realm", true, noRealmConfig, noRealmExpectedError) + + t.Log("Testing auth w/ full params variables") + fullParamsConfig := &auth_providers.CommandAuthConfigKerberos{ + Username: username, + Password: password, + Realm: realm, + ConfigPath: configPath, + } + authKerberosTest(t, "w/ full params variables", false, fullParamsConfig) + + t.Log("Testing auth w/ invalid password") + fullParamsInvalidPassConfig := &auth_providers.CommandAuthConfigKerberos{ + Username: username, + Password: "invalid-password", + Realm: realm, + ConfigPath: configPath, + } + invalidCredsExpectedError := []string{"failed to login", "Kerberos"} + authKerberosTest(t, "w/ invalid password", true, fullParamsInvalidPassConfig, invalidCredsExpectedError...) + + t.Log("Testing auth w/ username@realm format") + usernameRealmConfig := &auth_providers.CommandAuthConfigKerberos{ + Username: fmt.Sprintf("%s@%s", username, realm), + Password: password, + ConfigPath: configPath, + } + authKerberosTest(t, "w/ username@realm format", false, usernameRealmConfig) +} + +func TestCommandAuthConfigKerberos_Build(t *testing.T) { + // Skip test if TEST_KEYFACTOR_KRB_AUTH is not set + if os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "1" && os.Getenv("TEST_KEYFACTOR_KRB_AUTH") != "true" { + t.Skip("Skipping TestCommandAuthConfigKerberos_Build - set TEST_KEYFACTOR_KRB_AUTH=true to run") + return + } + + config := &auth_providers.CommandAuthConfigKerberos{ + Username: os.Getenv(auth_providers.EnvKeyfactorKrbUsername), + Password: os.Getenv(auth_providers.EnvKeyfactorKrbPassword), + Realm: os.Getenv(auth_providers.EnvKeyfactorKrbRealm), + ConfigPath: os.Getenv(auth_providers.EnvKeyfactorKrbConfig), + } + + authenticator, err := config.Build() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if authenticator == nil { + t.Fatalf("expected a non-nil Authenticator") + } +} + +// setKerberosEnvVariables sets the Kerberos environment variables +func setKerberosEnvVariables(username, password, realm, keytab, configPath, ccache string) { + os.Setenv(auth_providers.EnvKeyfactorKrbUsername, username) + os.Setenv(auth_providers.EnvKeyfactorKrbPassword, password) + os.Setenv(auth_providers.EnvKeyfactorKrbRealm, realm) + os.Setenv(auth_providers.EnvKeyfactorKrbKeytab, keytab) + os.Setenv(auth_providers.EnvKeyfactorKrbConfig, configPath) + os.Setenv(auth_providers.EnvKeyfactorKrbCCache, ccache) +} + +// exportKerberosEnvVariables exports the Kerberos environment variables +func exportKerberosEnvVariables() (string, string, string, string, string, string) { + username := os.Getenv(auth_providers.EnvKeyfactorKrbUsername) + password := os.Getenv(auth_providers.EnvKeyfactorKrbPassword) + realm := os.Getenv(auth_providers.EnvKeyfactorKrbRealm) + keytab := os.Getenv(auth_providers.EnvKeyfactorKrbKeytab) + configPath := os.Getenv(auth_providers.EnvKeyfactorKrbConfig) + ccache := os.Getenv(auth_providers.EnvKeyfactorKrbCCache) + return username, password, realm, keytab, configPath, ccache +} + +// unsetKerberosEnvVariables unsets the Kerberos environment variables +func unsetKerberosEnvVariables() { + os.Unsetenv(auth_providers.EnvKeyfactorKrbUsername) + os.Unsetenv(auth_providers.EnvKeyfactorKrbPassword) + os.Unsetenv(auth_providers.EnvKeyfactorKrbRealm) + os.Unsetenv(auth_providers.EnvKeyfactorKrbKeytab) + os.Unsetenv(auth_providers.EnvKeyfactorKrbConfig) + os.Unsetenv(auth_providers.EnvKeyfactorKrbCCache) +} + +func authKerberosTest( + t *testing.T, testName string, allowFail bool, config *auth_providers.CommandAuthConfigKerberos, + errorContains ...string, +) { + t.Run( + fmt.Sprintf("Kerberos Auth Test %s", testName), func(t *testing.T) { + + err := config.Authenticate() + if allowFail { + if err == nil { + t.Errorf("Kerberos auth test '%s' should have failed", testName) + t.FailNow() + return + } + if len(errorContains) > 0 { + for _, ec := range errorContains { + if !strings.Contains(err.Error(), ec) { + t.Errorf("Kerberos auth test '%s' failed with unexpected error %v", testName, err) + t.FailNow() + return + } + } + } + t.Logf("Kerberos auth test '%s' failed as expected with %v", testName, err) + return + } + if err != nil { + t.Errorf("Kerberos auth test '%s' failed with %v", testName, err) + t.FailNow() + return + } + }, + ) +} diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go index 720a9ae..66d7f8f 100644 --- a/auth_providers/auth_oauth_test.go +++ b/auth_providers/auth_oauth_test.go @@ -342,14 +342,14 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) { t.Log("Testing oAuth with invalid creds implicit config file") invProfileCreds := &auth_providers.CommandConfigOauth{} invProfileCreds. - WithConfigProfile("oauth_invalid_creds"). + WithConfigProfile("oauth-invalid-creds"). WithSkipVerify(true) authOauthTest(t, "with invalid creds implicit config file", true, invProfileCreds, invalidCredsExpectedError...) t.Log("Testing oAuth with invalid Command host implicit config file") invCmdHost := &auth_providers.CommandConfigOauth{} invCmdHost. - WithConfigProfile("oauth_invalid_host"). + WithConfigProfile("oauth-invalid-host"). WithSkipVerify(true) invHostExpectedError := []string{"no such host"} authOauthTest(t, "with invalid creds implicit config file", true, invCmdHost, invHostExpectedError...) diff --git a/auth_providers/command_config.go b/auth_providers/command_config.go index da895bf..e8a14a3 100644 --- a/auth_providers/command_config.go +++ b/auth_providers/command_config.go @@ -41,6 +41,12 @@ 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. + // 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. + KerberosConfig string `json:"kerberos_config,omitempty" yaml:"kerberos_config,omitempty"` // KerberosConfig is the path to krb5.conf. + KerberosCCache string `json:"kerberos_ccache,omitempty" yaml:"kerberos_ccache,omitempty"` // KerberosCCache is the path to the credential cache. + KerberosSPN string `json:"kerberos_spn,omitempty" yaml:"kerberos_spn,omitempty"` // KerberosSPN is the Service Principal Name. } // AuthProvider represents the authentication provider configuration. @@ -215,7 +221,12 @@ func (s *Server) Compare(other *Server) bool { s.APIPath == other.APIPath && s.SkipTLSVerify == other.SkipTLSVerify && s.CACertPath == other.CACertPath && - s.AuthType == other.AuthType + s.AuthType == other.AuthType && + s.KerberosRealm == other.KerberosRealm && + s.KerberosKeytab == other.KerberosKeytab && + s.KerberosConfig == other.KerberosConfig && + s.KerberosCCache == other.KerberosCCache && + s.KerberosSPN == other.KerberosSPN } // MergeConfigFromFile merges the configuration from a file into the existing Config. @@ -253,6 +264,8 @@ func MergeConfigFromFile(filePath string, config *Config) (*Config, error) { func (s *Server) GetAuthType() string { if (s.ClientID != "" && s.ClientSecret != "") || s.AccessToken != "" { s.AuthType = "oauth" + } else if s.KerberosRealm != "" || s.KerberosKeytab != "" || s.KerberosCCache != "" { + s.AuthType = "kerberos" } else if s.Username != "" && s.Password != "" { s.AuthType = "basic" } else { @@ -325,6 +338,40 @@ func (s *Server) GetOAuthClientConfig() (*CommandConfigOauth, error) { return &oauthConfig, nil } +// GetKerberosClientConfig returns the Kerberos configuration for the client. +func (s *Server) GetKerberosClientConfig() (*CommandAuthConfigKerberos, error) { + configType := s.GetAuthType() + if configType != "kerberos" { + return nil, fmt.Errorf("invalid auth type: %s", configType) + } + baseConfig := CommandAuthConfig{} + baseConfig. + WithCommandHostName(s.Host). + WithCommandPort(s.Port). + WithCommandAPIPath(s.APIPath). + WithCommandCACert(s.CACertPath). + WithSkipVerify(s.SkipTLSVerify) + + kerberosConfig := CommandAuthConfigKerberos{ + CommandAuthConfig: baseConfig, + } + kerberosConfig. + WithUsername(s.Username). + WithPassword(s.Password). + WithRealm(s.KerberosRealm). + WithKeytabPath(s.KerberosKeytab). + WithConfigPath(s.KerberosConfig). + WithCCachePath(s.KerberosCCache). + WithSPN(s.KerberosSPN). + Build() + + vErr := kerberosConfig.ValidateAuthConfig() + if vErr != nil { + return nil, vErr + } + return &kerberosConfig, nil +} + // Example usage of Config // // This example demonstrates how to use Config to read and write server configurations. diff --git a/go.mod b/go.mod index 79537f1..be4c892 100644 --- a/go.mod +++ b/go.mod @@ -14,32 +14,37 @@ module github.com/Keyfactor/keyfactor-auth-client-go -go 1.24 - -toolchain go1.24.3 +go 1.24.0 require ( - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 - github.com/stretchr/testify v1.10.0 - golang.org/x/oauth2 v0.30.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 + github.com/jcmturner/gokrb5/v8 v8.4.4 + github.com/stretchr/testify v1.11.1 + golang.org/x/oauth2 v0.34.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 43c21a3..9ac55df 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,47 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -36,27 +54,77 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/config/auth_config_schema.json b/lib/config/auth_config_schema.json index 2af7d48..32661a7 100644 --- a/lib/config/auth_config_schema.json +++ b/lib/config/auth_config_schema.json @@ -42,6 +42,32 @@ "type": "string", "description": "The Active Directory domain to authenticate with using basic auth" }, + "kerberos_realm": { + "type": "string", + "description": "The Kerberos realm (uppercase, e.g., EXAMPLE.COM)" + }, + "kerberos_keytab": { + "type": "string", + "description": "The path to the Kerberos keytab file for keytab-based authentication" + }, + "kerberos_config": { + "type": "string", + "description": "The path to the krb5.conf file", + "default": "/etc/krb5.conf" + }, + "kerberos_ccache": { + "type": "string", + "description": "The path to the Kerberos credential cache file" + }, + "kerberos_spn": { + "type": "string", + "description": "The Service Principal Name (optional, auto-generated from host as HTTP/hostname)" + }, + "kerberos_disable_pafxfast": { + "type": "boolean", + "description": "Disable PA-FX-FAST for Active Directory compatibility", + "default": false + }, "api_path": { "type": "string", "description": "The path to the Keyfactor Command API", @@ -87,27 +113,79 @@ }, "oneOf": [ { + "title": "Basic Auth", "required": [ "username", "password" ], "not": { - "required": [ - "client_id", - "client_secret" + "anyOf": [ + { "required": ["client_id"] }, + { "required": ["kerberos_realm"] }, + { "required": ["kerberos_keytab"] }, + { "required": ["kerberos_ccache"] } ] } }, { + "title": "OAuth2", "required": [ "client_id", "client_secret", "token_url" ], "not": { - "required": [ - "username", - "password" + "anyOf": [ + { "required": ["domain"] }, + { "required": ["kerberos_realm"] }, + { "required": ["kerberos_keytab"] }, + { "required": ["kerberos_ccache"] } + ] + } + }, + { + "title": "Kerberos Password Auth", + "required": [ + "username", + "password", + "kerberos_realm" + ], + "not": { + "anyOf": [ + { "required": ["client_id"] }, + { "required": ["domain"] }, + { "required": ["kerberos_keytab"] }, + { "required": ["kerberos_ccache"] } + ] + } + }, + { + "title": "Kerberos Keytab Auth", + "required": [ + "username", + "kerberos_keytab", + "kerberos_realm" + ], + "not": { + "anyOf": [ + { "required": ["client_id"] }, + { "required": ["domain"] }, + { "required": ["password"] }, + { "required": ["kerberos_ccache"] } + ] + } + }, + { + "title": "Kerberos CCache Auth", + "required": [ + "kerberos_ccache" + ], + "not": { + "anyOf": [ + { "required": ["client_id"] }, + { "required": ["domain"] }, + { "required": ["password"] }, + { "required": ["kerberos_keytab"] } ] } } diff --git a/lib/config/full_auth_config_example.json b/lib/config/full_auth_config_example.json index c2bb73c..380abd4 100644 --- a/lib/config/full_auth_config_example.json +++ b/lib/config/full_auth_config_example.json @@ -35,6 +35,30 @@ "vault_name": "keyfactor-secrets" } } + }, + "kerberos_password": { + "host": "keyfactor3.command.kfdelivery.com", + "username": "svc_keyfactor", + "password": "password", + "kerberos_realm": "KFDELIVERY.COM", + "kerberos_config": "/etc/krb5.conf", + "kerberos_disable_pafxfast": true, + "api_path": "KeyfactorAPI" + }, + "kerberos_keytab": { + "host": "keyfactor4.command.kfdelivery.com", + "username": "svc_keyfactor", + "kerberos_realm": "KFDELIVERY.COM", + "kerberos_keytab": "/etc/keyfactor/svc_keyfactor.keytab", + "kerberos_config": "/etc/krb5.conf", + "kerberos_spn": "HTTP/keyfactor4.command.kfdelivery.com", + "api_path": "KeyfactorAPI" + }, + "kerberos_ccache": { + "host": "keyfactor5.command.kfdelivery.com", + "kerberos_ccache": "/tmp/krb5cc_1000", + "kerberos_config": "/etc/krb5.conf", + "api_path": "KeyfactorAPI" } } } \ No newline at end of file diff --git a/lib/config/kerberos_config_example.json b/lib/config/kerberos_config_example.json new file mode 100644 index 0000000..55346ac --- /dev/null +++ b/lib/config/kerberos_config_example.json @@ -0,0 +1,28 @@ +{ + "servers": { + "default": { + "host": "keyfactor.command.kfdelivery.com", + "username": "svc_keyfactor", + "password": "password", + "kerberos_realm": "KFDELIVERY.COM", + "kerberos_config": "/etc/krb5.conf", + "kerberos_disable_pafxfast": true, + "api_path": "KeyfactorAPI" + }, + "keytab_auth": { + "host": "keyfactor2.command.kfdelivery.com", + "username": "svc_keyfactor", + "kerberos_realm": "KFDELIVERY.COM", + "kerberos_keytab": "/etc/keyfactor/svc_keyfactor.keytab", + "kerberos_config": "/etc/krb5.conf", + "kerberos_spn": "HTTP/keyfactor2.command.kfdelivery.com", + "api_path": "KeyfactorAPI" + }, + "ccache_auth": { + "host": "keyfactor3.command.kfdelivery.com", + "kerberos_ccache": "/tmp/krb5cc_1000", + "kerberos_config": "/etc/krb5.conf", + "api_path": "KeyfactorAPI" + } + } +}