From 9a7c3ed447ac3539bdffd8e56f73050b2ee60249 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 3 May 2024 08:59:27 -0700
Subject: [PATCH 01/57] feat: Initial commit
---
.gitignore | 139 ++++++++++++++++
README.md | 37 ++++-
auth_providers/active_directory/ad_auth.go | 76 +++++++++
auth_providers/active_directory/models.go | 16 ++
auth_providers/auth_core.go | 31 ++++
.../keycloak/keycloak_auth_certificate.go | 1 +
.../keycloak_auth_client_credentials.go | 157 ++++++++++++++++++
auth_providers/keycloak/models.go | 56 +++++++
auth_providers/models.go | 28 ++++
go.mod | 3 +
main.go | 21 +++
scripts/auth_keycloak.sh | 54 ++++++
12 files changed, 618 insertions(+), 1 deletion(-)
create mode 100644 .gitignore
create mode 100644 auth_providers/active_directory/ad_auth.go
create mode 100644 auth_providers/active_directory/models.go
create mode 100644 auth_providers/auth_core.go
create mode 100644 auth_providers/keycloak/keycloak_auth_certificate.go
create mode 100644 auth_providers/keycloak/keycloak_auth_client_credentials.go
create mode 100644 auth_providers/keycloak/models.go
create mode 100644 auth_providers/models.go
create mode 100644 go.mod
create mode 100644 main.go
create mode 100755 scripts/auth_keycloak.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9ab1d97
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,139 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Terraform template
+# Local .terraform directories
+**/.terraform/*
+
+# .tfstate files
+*.tfstate
+*.tfstate.*
+
+# Crash log files
+crash.log
+crash.*.log
+
+# Exclude all .tfvars files, which are likely to contain sensitive data, such as
+# password, private keys, and other secrets. These should not be part of version
+# control as they are data points which are potentially sensitive and subject
+# to change depending on the environment.
+*.tfvars
+*.tfvars.json
+
+# Ignore override files as they are usually used to override resources locally and so
+# are not checked in
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Include override files you do wish to add to version control using negated pattern
+# !example_override.tf
+
+# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
+# example: *tfplan*
+
+# Ignore CLI configuration files
+.terraformrc
+terraform.rc
+
+### Go template
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+
+*.env*
\ No newline at end of file
diff --git a/README.md b/README.md
index 8e27d92..e8bee6a 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,36 @@
-# keyfactor-auth-client-go
\ No newline at end of file
+# keyfactor-auth-client-go
+Client library for authenticating to Keyfactor Command
+
+## Environment Variables
+
+### Global
+
+| Name | Description | Default |
+|--------------------|------------------------------------------------------|---------------|
+| KEYFACTOR_HOSTNAME | Keyfactor Command hostname without protocol and port | |
+| KEYFACTOR_PORT | Keyfactor Command port | `443` |
+| KEYFACTOR_API_PATH | Keyfactor Command API Path | /KeyfactorAPI |
+
+### Active Directory
+
+| Name | Description | Default |
+|--------------------|---------------------------------------------------------------------------------------------|---------|
+| KEYFACTOR_USERNAME | Active Directory username to authenticate to Keyfactor Command API | |
+| KEYFACTOR_PASSWORD | Password associated with Active Directory username to authenticate to Keyfactor Command API | |
+| KEYFACTOR_DOMAIN | Active Directory domain of user. Can be implied from username if it contains `@` or `\\` | |
+
+### Keycloak
+
+| Name | Description | Default |
+|--------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------|
+| KEYFACTOR_AUTH_HOST_NAME | Hostname of Keycloak instance to authenticate to Keyfactor Command | |
+| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
+| KEYFACTOR_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | |
+| KEYFACTOR_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
+
+#### Client Credentials
+
+| Name | Description | Default |
+|------------------------------|------------------------------|---------|
+| KEYFACTOR_AUTH_CLIENT_ID | Keyfactor Auth Client ID | |
+| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
new file mode 100644
index 0000000..5c661ab
--- /dev/null
+++ b/auth_providers/active_directory/ad_auth.go
@@ -0,0 +1,76 @@
+package active_directory
+
+import (
+ "encoding/base64"
+ "fmt"
+ "os"
+ "strings"
+)
+
+func (c *CommandAuthConfigActiveDirectory) Authenticate() error {
+ cErr := c.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+
+ c.AuthHeader = fmt.Sprintf("Basic %s", c.getBasicAuthHeader())
+ return nil
+}
+
+func (c *CommandAuthConfigActiveDirectory) getBasicAuthHeader() string {
+ authStr := fmt.Sprintf("%s@%s:%s", c.Domain, c.Username, c.Password)
+ return base64.StdEncoding.EncodeToString([]byte(authStr))
+}
+
+func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
+ cErr := c.CommandAuthConfigBasic.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+
+ if c.Username == "" {
+ if username, ok := os.LookupEnv(EnvKeyfactorUsername); ok {
+ c.Username = username
+ } else {
+ return fmt.Errorf("username or environment variable %s is required", EnvKeyfactorUsername)
+ }
+ }
+
+ if c.Password == "" {
+ if password, ok := os.LookupEnv(EnvKeyfactorPassword); ok {
+ c.Password = password
+ } else {
+ return fmt.Errorf("password or environment variable KEYFACTOR_PASSWORD is required")
+ }
+ }
+
+ if c.Domain == "" {
+ if domain, ok := os.LookupEnv("KEYFACTOR_DOMAIN"); ok {
+ c.Domain = domain
+ } else {
+ //check if domain is in username with @ or \\
+ if strings.Contains(c.Username, "@") {
+ domain := strings.Split(c.Username, "@")
+ if len(domain) != 2 {
+ return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
+ }
+ c.Username = domain[0] // remove domain from username
+ c.Domain = domain[1]
+ } else if strings.Contains(c.Username, "\\") {
+ domain := strings.Split(c.Username, "\\")
+ if len(domain) != 2 {
+ return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
+ }
+ c.Domain = domain[0]
+ c.Username = domain[1] // remove domain from username
+ } else {
+ return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
+ }
+ }
+ }
+ return nil
+}
+
+func (c *CommandAuthConfigActiveDirectory) GetAuthHeader() string {
+ return c.AuthHeader
+}
diff --git a/auth_providers/active_directory/models.go b/auth_providers/active_directory/models.go
new file mode 100644
index 0000000..cd157ba
--- /dev/null
+++ b/auth_providers/active_directory/models.go
@@ -0,0 +1,16 @@
+package active_directory
+
+import (
+ "keyfactor_auth/auth_providers"
+)
+
+const (
+ EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
+ EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
+ EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
+)
+
+type CommandAuthConfigActiveDirectory struct {
+ auth_providers.CommandAuthConfigBasic
+ Domain string `json:"domain"`
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
new file mode 100644
index 0000000..eeab654
--- /dev/null
+++ b/auth_providers/auth_core.go
@@ -0,0 +1,31 @@
+package auth_providers
+
+import (
+ "fmt"
+ "os"
+)
+
+func (c *CommandAuthConfig) ValidateAuthConfig() error {
+ if c.CommandHostName == "" {
+ if hostName, ok := os.LookupEnv(EnvKeyfactorHostName); ok {
+ c.CommandHostName = hostName
+ } else {
+ return fmt.Errorf("command_host_name or environment variable %s is required", EnvKeyfactorHostName)
+ }
+ }
+ if c.CommandPort == "" {
+ if port, ok := os.LookupEnv(EnvKeyfactorPort); ok {
+ c.CommandPort = port
+ } else {
+ c.CommandPort = DefaultCommandPort
+ }
+ }
+ if c.CommandAPIPath == "" {
+ if apiPath, ok := os.LookupEnv(EnvKeyfactorAPIPath); ok {
+ c.CommandAPIPath = apiPath
+ } else {
+ c.CommandAPIPath = DefaultCommandAPIPath
+ }
+ }
+ return nil
+}
diff --git a/auth_providers/keycloak/keycloak_auth_certificate.go b/auth_providers/keycloak/keycloak_auth_certificate.go
new file mode 100644
index 0000000..cf172a0
--- /dev/null
+++ b/auth_providers/keycloak/keycloak_auth_certificate.go
@@ -0,0 +1 @@
+package keycloak
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
new file mode 100644
index 0000000..b564907
--- /dev/null
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -0,0 +1,157 @@
+package keycloak
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+)
+
+const (
+ DefaultKeyfactorAuthPort = "8444"
+ DefaultKeyfactorRealm = "Keyfactor"
+ EnvKeyfactorClientID = "KEYFACTOR_CLIENT_ID"
+ EnvKeyfactorClientSecret = "KEYFACTOR_CLIENT_SECRET"
+ EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
+ EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
+)
+
+func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
+ c.HttpClient = &http.Client{}
+ c.AuthType = "client_credentials"
+ cErr := c.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+
+ token, tErr := c.GetToken()
+ if tErr != nil {
+ return tErr
+ }
+ if token == "" {
+ return fmt.Errorf("failed to get Bearer token using client credentials")
+ }
+
+ c.AuthHeader = fmt.Sprintf("Bearer %s", token)
+
+ return nil
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
+ cErr := c.CommandAuthConfigKeyCloak.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+
+ if c.ClientID == "" {
+ if clientID, ok := os.LookupEnv(EnvKeyfactorClientID); ok {
+ c.ClientID = clientID
+ } else {
+ return fmt.Errorf("client_id or environment variable %s is required", EnvKeyfactorClientID)
+ }
+ }
+ if c.ClientSecret == "" {
+ if clientSecret, ok := os.LookupEnv(EnvKeyfactorClientSecret); ok {
+ c.ClientSecret = clientSecret
+ } else {
+ return fmt.Errorf("client_secret or environment variable %s is required", EnvKeyfactorClientSecret)
+ }
+ }
+ if c.Realm == "" {
+ if realm, ok := os.LookupEnv(EnvKeyfactorAuthRealm); ok {
+ c.Realm = realm
+ } else {
+ c.Realm = DefaultKeyfactorRealm
+ }
+ }
+
+ if c.TokenURL == "" {
+ if tokenURL, ok := os.LookupEnv(EnvKeyfactorAuthTokenURL); ok {
+ c.TokenURL = tokenURL
+ } else {
+ c.TokenURL = fmt.Sprintf(
+ "https://%s:%s/realms/%s/protocol/openid-connect/token",
+ c.AuthHostName,
+ c.AuthPort,
+ c.Realm,
+ )
+ }
+ }
+ return nil
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
+ if c.AccessToken != "" && time.Now().Before(c.Expiry) {
+ return c.AccessToken, nil
+ }
+
+ // Use refresh token if available and not expired
+ if c.RefreshToken != "" && time.Now().After(c.Expiry) {
+ return c.refreshAccessToken()
+ }
+
+ // Otherwise, get a new access token using client credentials
+ return c.requestNewToken()
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) requestNewToken() (string, error) {
+ formData := url.Values{}
+ formData.Set("grant_type", "client_credentials")
+ formData.Set("client_id", c.ClientID)
+ formData.Set("client_secret", c.ClientSecret)
+
+ return c.doTokenRequest(formData.Encode())
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) refreshAccessToken() (string, error) {
+ formData := url.Values{}
+ formData.Set("grant_type", "refresh_token")
+ formData.Set("client_id", c.ClientID)
+ formData.Set("client_secret", c.ClientSecret)
+ formData.Set("refresh_token", c.RefreshToken)
+ return c.doTokenRequest(formData.Encode())
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) doTokenRequest(data string) (string, error) {
+ requestBody := strings.NewReader(data)
+ req, reqErr := http.NewRequest("POST", c.TokenURL, requestBody)
+ if reqErr != nil {
+ return "", reqErr
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ resp, tkRespErr := c.HttpClient.Do(req)
+ if tkRespErr != nil {
+ return "", tkRespErr
+ }
+ defer resp.Body.Close()
+
+ // check response status code
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+
+ body, ioErr := io.ReadAll(resp.Body)
+ if ioErr != nil {
+ return "", ioErr
+ }
+
+ var tokenResponse struct {
+ AccessToken string `json:"access_token"`
+ RefreshToken string `json:"refresh_token"`
+ ExpiresIn int `json:"expires_in"`
+ }
+ if err := json.Unmarshal(body, &tokenResponse); err != nil {
+ return "", err
+ }
+
+ c.AccessToken = tokenResponse.AccessToken
+ c.RefreshToken = tokenResponse.RefreshToken
+ c.Expiry = time.Now().Add(time.Duration(tokenResponse.ExpiresIn-30) * time.Second) // Subtract 30 seconds to account for delay
+
+ return c.AccessToken, nil
+}
diff --git a/auth_providers/keycloak/models.go b/auth_providers/keycloak/models.go
new file mode 100644
index 0000000..c5aa986
--- /dev/null
+++ b/auth_providers/keycloak/models.go
@@ -0,0 +1,56 @@
+package keycloak
+
+import (
+ "os"
+ "time"
+
+ "keyfactor_auth/auth_providers"
+)
+
+const (
+ EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOST_NAME"
+ EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
+ EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
+)
+
+type CommandAuthConfigKeyCloak struct {
+ auth_providers.CommandAuthConfig
+ AuthHostName string `json:"auth_host_name"`
+ AuthPort string `json:"auth_port"`
+ AuthType string `json:"auth_type"` // The type of Keycloak auth to use such as client_credentials, password, etc.
+}
+
+func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
+ pErr := c.CommandAuthConfig.ValidateAuthConfig()
+ if pErr != nil {
+ return pErr
+ }
+
+ if c.AuthHostName == "" {
+ if authHostName, ok := os.LookupEnv(EnvKeyfactorAuthHostname); ok {
+ c.AuthHostName = authHostName
+ } else {
+ c.AuthHostName = c.CommandHostName
+ }
+ }
+ if c.AuthPort == "" {
+ if port, ok := os.LookupEnv(EnvKeyfactorAuthPort); ok {
+ c.AuthPort = port
+ } else {
+ c.AuthPort = DefaultKeyfactorAuthPort
+ }
+ }
+ return nil
+
+}
+
+type CommandAuthKeyCloakClientCredentials struct {
+ CommandAuthConfigKeyCloak
+ ClientID string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+ AccessToken string `json:"access_token"`
+ RefreshToken string `json:"refresh_token"`
+ Expiry time.Time `json:"expiry"`
+ Realm string `json:"realm"`
+ TokenURL string `json:"token_url"`
+}
diff --git a/auth_providers/models.go b/auth_providers/models.go
new file mode 100644
index 0000000..7659d8e
--- /dev/null
+++ b/auth_providers/models.go
@@ -0,0 +1,28 @@
+package auth_providers
+
+import (
+ "net/http"
+)
+
+type CommandAuthConfig struct {
+ ConfigType string `json:"config_type"`
+ AuthHeader string `json:"auth_header"`
+ CommandHostName string `json:"command_host_name"`
+ CommandPort string `json:"command_port"`
+ CommandAPIPath string `json:"command_api_path"`
+ HttpClient *http.Client
+}
+
+const (
+ DefaultCommandPort = "443"
+ DefaultCommandAPIPath = "KeyfactorAPI"
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+)
+
+type CommandAuthConfigBasic struct {
+ CommandAuthConfig
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..bbe8ccd
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module keyfactor_auth
+
+go 1.22
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..e36cb7b
--- /dev/null
+++ b/main.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "log"
+
+ "keyfactor_auth/auth_providers/keycloak"
+)
+
+func main() {
+ //try client auth
+ keyfactorAuthConfig := keycloak.CommandAuthKeyCloakClientCredentials{}
+ aErr := keyfactorAuthConfig.Authenticate()
+ if aErr != nil {
+ log.Fatalf("[ERROR] %s\n", aErr)
+ }
+ log.Println("[INFO] Successfully authenticated with Keyfactor")
+ log.Println("[INFO] Token: ", keyfactorAuthConfig.AccessToken)
+ log.Println("[INFO] Refresh Token: ", keyfactorAuthConfig.RefreshToken)
+ log.Println("[INFO] Token Expiry: ", keyfactorAuthConfig.Expiry)
+
+}
diff --git a/scripts/auth_keycloak.sh b/scripts/auth_keycloak.sh
new file mode 100755
index 0000000..3f2554b
--- /dev/null
+++ b/scripts/auth_keycloak.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+# Input vars
+function checkVars() {
+ # Check hostname
+ if [ -z "$KEYFACTOR_AUTH_HOSTNAME" ]; then
+ # Check if KEYFACTOR_HOSTNAME is set
+ if [ -z "$KEYFACTOR_HOSTNAME" ]; then
+ echo "KEYFACTOR_HOSTNAME is not set"
+ # prompt for hostname until it is set
+ while [ -z "$KEYFACTOR_HOSTNAME" ]; do
+ read -p "Enter auth hostname: " KEYFACTOR_AUTH_HOSTNAME
+ done
+ else
+ echo "Setting auth hostname to $KEYFACTOR_HOSTNAME"
+ KEYFACTOR_AUTH_HOSTNAME="$KEYFACTOR_HOSTNAME"
+ fi
+ fi
+
+ if [ -z "$KEYFACTOR_CLIENT_ID" ]; then
+ echo "KEYFACTOR_CLIENT_ID is not set"
+ # prompt for client_id until it is set
+ while [ -z "$KEYFACTOR_CLIENT_ID" ]; do
+ read -p "Enter client_id: " KEYFACTOR_CLIENT_ID
+ done
+ fi
+
+ if [ -z "$KEYFACTOR_CLIENT_SECRET" ]; then
+ echo "KEYFACTOR_CLIENT_SECRET is not set"
+ while [ -z "$KEYFACTOR_CLIENT_SECRET" ]; do
+ #prompt for sensitive client_secret until it is set
+ read -s -p "Enter client_secret: " KEYFACTOR_CLIENT_SECRET
+ done
+ fi
+}
+
+function authClientCredentials(){
+ checkVars
+ client_id="${KEYFACTOR_CLIENT_ID}"
+ client_secret="${KEYFACTOR_CLIENT_SECRET}"
+ grant_type="client_credentials"
+ auth_url="https://$KEYFACTOR_AUTH_HOSTNAME:$KEYFACTOR_AUTH_PORT/realms/$KEYFFACTOR_AUTH_REALM/protocol/openid-connect/token"
+
+ curl -X POST $auth_url \
+ --header 'Content-Type: application/x-www-form-urlencoded' \
+ --data-urlencode "grant_type=$grant_type" \
+ --data-urlencode "client_id=$client_id" \
+ --data-urlencode "client_secret=$client_secret" > keyfactor_auth.json
+
+ export KEYFACTOR_ACCESS_TOKEN=$(cat keyfactor_auth.json | jq -r '.access_token')
+
+}
+
+authClientCredentials
\ No newline at end of file
From 32d7f1b5d981f524c8336d8e70a27e8b079d9a72 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 3 May 2024 09:26:21 -0700
Subject: [PATCH 02/57] feat(script): Adding powershell script for auth against
Keycloak.
---
scripts/auth_keycloak.ps1 | 50 +++++++++++++++++++++++++++++++++++++++
scripts/auth_keycloak.sh | 2 +-
2 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 scripts/auth_keycloak.ps1
diff --git a/scripts/auth_keycloak.ps1 b/scripts/auth_keycloak.ps1
new file mode 100644
index 0000000..3e97c9c
--- /dev/null
+++ b/scripts/auth_keycloak.ps1
@@ -0,0 +1,50 @@
+function CheckVars {
+ # Check hostname
+ if (!$env:KEYFACTOR_AUTH_HOSTNAME) {
+ # Check if KEYFACTOR_HOSTNAME is set
+ if (!$env:KEYFACTOR_HOSTNAME) {
+ Write-Host "KEYFACTOR_HOSTNAME is not set"
+ # prompt for hostname until it is set
+ while (!$env:KEYFACTOR_HOSTNAME) {
+ $env:KEYFACTOR_AUTH_HOSTNAME = Read-Host "Enter auth hostname"
+ }
+ } else {
+ Write-Host "Setting auth hostname to $env:KEYFACTOR_HOSTNAME"
+ $env:KEYFACTOR_AUTH_HOSTNAME = $env:KEYFACTOR_HOSTNAME
+ }
+ }
+
+ if (!$env:KEYFACTOR_CLIENT_ID) {
+ Write-Host "KEYFACTOR_CLIENT_ID is not set"
+ # prompt for client_id until it is set
+ while (!$env:KEYFACTOR_CLIENT_ID) {
+ $env:KEYFACTOR_CLIENT_ID = Read-Host "Enter client_id"
+ }
+ }
+
+ if (!$env:KEYFACTOR_CLIENT_SECRET) {
+ Write-Host "KEYFACTOR_CLIENT_SECRET is not set"
+ while (!$env:KEYFACTOR_CLIENT_SECRET) {
+ #prompt for sensitive client_secret until it is set
+ $env:KEYFACTOR_CLIENT_SECRET = Read-Host "Enter client_secret"
+ }
+ }
+}
+
+function AuthClientCredentials {
+ CheckVars
+ $client_id = $env:KEYFACTOR_CLIENT_ID
+ $client_secret = $env:KEYFACTOR_CLIENT_SECRET
+ $grant_type = "client_credentials"
+ $auth_url = "https://$env:KEYFACTOR_AUTH_HOSTNAME:$($env:KEYFACTOR_AUTH_PORT -replace '^$', '8444')/realms/$($env:KEYFACTOR_AUTH_REALM -replace '^$', 'Keyfactor')/protocol/openid-connect/token"
+
+ $response = Invoke-RestMethod -Uri $auth_url -Method POST -Body @{
+ "grant_type" = $grant_type
+ "client_id" = $client_id
+ "client_secret" = $client_secret
+ } -ContentType 'application/x-www-form-urlencoded'
+
+ $env:KEYFACTOR_ACCESS_TOKEN = $response.access_token
+}
+
+AuthClientCredentials
\ No newline at end of file
diff --git a/scripts/auth_keycloak.sh b/scripts/auth_keycloak.sh
index 3f2554b..b01f7a4 100755
--- a/scripts/auth_keycloak.sh
+++ b/scripts/auth_keycloak.sh
@@ -39,7 +39,7 @@ function authClientCredentials(){
client_id="${KEYFACTOR_CLIENT_ID}"
client_secret="${KEYFACTOR_CLIENT_SECRET}"
grant_type="client_credentials"
- auth_url="https://$KEYFACTOR_AUTH_HOSTNAME:$KEYFACTOR_AUTH_PORT/realms/$KEYFFACTOR_AUTH_REALM/protocol/openid-connect/token"
+ auth_url="https://$KEYFACTOR_AUTH_HOSTNAME:${KEYFACTOR_AUTH_PORT:-8444}/realms/${KEYFFACTOR_AUTH_REALM:-Keyfactor}/protocol/openid-connect/token"
curl -X POST $auth_url \
--header 'Content-Type: application/x-www-form-urlencoded' \
From d2a5c807e16dba0dec8e54d685ab0b4234cd1290 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 3 May 2024 10:23:58 -0700
Subject: [PATCH 03/57] chore(docs): Add license headers feat(pkg): Add
Makefile for build ops and pkg versioning.
---
Makefile | 80 +++++++++++++++++++
README.md | 10 +++
auth_providers/active_directory/ad_auth.go | 14 ++++
auth_providers/active_directory/models.go | 16 +++-
auth_providers/auth_core.go | 14 ++++
.../keycloak/keycloak_auth_certificate.go | 14 ++++
.../keycloak_auth_client_credentials.go | 14 ++++
.../keycloak_auth_client_credentials_test.go | 75 +++++++++++++++++
auth_providers/keycloak/models.go | 16 +++-
auth_providers/models.go | 14 ++++
go.mod | 16 +++-
main.go | 32 +++++---
pkg/version.go | 7 ++
scripts/auth_keycloak.ps1 | 14 ++++
scripts/auth_keycloak.sh | 14 ++++
15 files changed, 334 insertions(+), 16 deletions(-)
create mode 100644 Makefile
create mode 100644 auth_providers/keycloak/keycloak_auth_client_credentials_test.go
create mode 100644 pkg/version.go
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fd0b4fb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,80 @@
+PROVIDER_DIR := $(PWD)
+TEST?=$$(go list ./... | grep -v 'vendor')
+HOSTNAME=keyfactor.com
+GOFMT_FILES := $$(find $(PROVIDER_DIR) -name '*.go' |grep -v vendor)
+NAMESPACE=keyfactor
+WEBSITE_REPO=https://github.com/Keyfactor/keyfactor-auth-client
+NAME=keyfactor-auth-client
+BINARY=${NAME}
+VERSION := $(GITHUB_REF_NAME)
+ifeq ($(VERSION),)
+ VERSION := v1.0.0
+endif
+OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH)
+BASEDIR := ${HOME}/go/bin
+INSTALLDIR := ${BASEDIR}
+MARKDOWN_FILE := README.md
+TEMP_TOC_FILE := temp_toc.md
+
+
+
+default: build
+
+build: fmt
+ go install
+
+release:
+ GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=freebsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$(git rev-parse HEAD)'"
+ GOOS=freebsd GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_freebsd_arm -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=linux GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_linux_386 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=openbsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_openbsd_386 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=openbsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=solaris GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_solaris_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+ GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 -ldflags "-X 'keyfactor_auth_client/pkg.Version=${VERSION}' -X 'keyfactor_auth_client/pkg.BuildTime=$$(date)' -X 'keyfactor_auth_client/pkg.CommitHash=$$(git rev-parse HEAD)'"
+
+install: fmt
+ go build -o ${BINARY}
+ rm -rf ${INSTALLDIR}/${BINARY}
+ mkdir -p ${INSTALLDIR}
+ chmod oug+x ${BINARY}
+ cp ${BINARY} ${INSTALLDIR}
+ mkdir -p ${HOME}/.local/bin || true
+ mv ${BINARY} ${HOME}/.local/bin/${BINARY}
+
+vendor:
+ go mod vendor
+
+version:
+ @echo ${VERSION}
+
+setversion:
+ sed -i '' -e 's/VERSION = ".*"/VERSION = "$(VERSION)"/' pkg/version/version.go
+
+test:
+ go test -i $(TEST) || exit 1
+ echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4
+
+fmt:
+ gofmt -w $(GOFMT_FILES)
+
+prerelease: fmt setversion
+ git tag -d $(VERSION) || true
+ git push origin :$(VERSION) || true
+ git tag $(VERSION)
+ git push origin $(VERSION)
+
+check_toc:
+ @grep -q 'TOC_START' $(MARKDOWN_FILE) && echo "TOC already exists." || (echo "TOC not found. Generating..." && $(MAKE) generate_toc)
+
+generate_toc:
+ # check if markdown-toc is installed and if not install it
+ @command -v markdown-toc >/dev/null 2>&1 || (echo "markdown-toc is not installed. Installing..." && npm install -g markdown-toc)
+ markdown-toc -i $(MARKDOWN_FILE) --skip 'Table of Contents'
+
+
+.PHONY: build prerelease release install test fmt vendor version setversion
\ No newline at end of file
diff --git a/README.md b/README.md
index e8bee6a..de9d97d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,16 @@
# keyfactor-auth-client-go
Client library for authenticating to Keyfactor Command
+
+
+- [Environment Variables](#environment-variables)
+ * [Global](#global)
+ * [Active Directory](#active-directory)
+ * [Keycloak](#keycloak)
+ + [Client Credentials](#client-credentials)
+
+
+
## Environment Variables
### Global
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
index 5c661ab..48c4d66 100644
--- a/auth_providers/active_directory/ad_auth.go
+++ b/auth_providers/active_directory/ad_auth.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 active_directory
import (
diff --git a/auth_providers/active_directory/models.go b/auth_providers/active_directory/models.go
index cd157ba..e6d71d8 100644
--- a/auth_providers/active_directory/models.go
+++ b/auth_providers/active_directory/models.go
@@ -1,7 +1,21 @@
+// Copyright 2024 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 active_directory
import (
- "keyfactor_auth/auth_providers"
+ "keyfactor_auth_client/auth_providers"
)
const (
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index eeab654..32aeaf4 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 (
diff --git a/auth_providers/keycloak/keycloak_auth_certificate.go b/auth_providers/keycloak/keycloak_auth_certificate.go
index cf172a0..cb44be7 100644
--- a/auth_providers/keycloak/keycloak_auth_certificate.go
+++ b/auth_providers/keycloak/keycloak_auth_certificate.go
@@ -1 +1,15 @@
+// Copyright 2024 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 keycloak
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index b564907..15ae01a 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 keycloak
import (
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
new file mode 100644
index 0000000..0546d3a
--- /dev/null
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
@@ -0,0 +1,75 @@
+// Copyright 2024 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 keycloak
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestCommandAuthKeyCloakClientCredentials_AuthenticateMocked(t *testing.T) {
+ // Create a test server that returns a 200 status and a token
+ ts := httptest.NewServer(
+ http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"access_token": "test_token", "expires_in": 3600}`))
+ },
+ ),
+ )
+ defer ts.Close()
+
+ // Create a new CommandAuthKeyCloakClientCredentials instance
+ c := &CommandAuthKeyCloakClientCredentials{
+ ClientID: "test_client_id",
+ ClientSecret: "test_client_secret",
+ Realm: "test_realm",
+ TokenURL: ts.URL, // Use the URL of the test server
+ }
+
+ // Call the Authenticate method
+ err := c.Authenticate()
+ if err != nil {
+ t.Errorf("Authenticate() error = %v", err)
+ return
+ }
+
+ // Check that the AuthHeader was set correctly
+ expectedAuthHeader := "Bearer test_token"
+ if c.AuthHeader != expectedAuthHeader {
+ t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
+ }
+}
+
+func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
+
+ // Create a new CommandAuthKeyCloakClientCredentials instance
+ c := &CommandAuthKeyCloakClientCredentials{} //Used environment configuration
+
+ // Call the Authenticate method
+ err := c.Authenticate()
+ if err != nil {
+ t.Errorf("Authenticate() error = %v", err)
+ return
+ }
+
+ // Check that the AuthHeader was set correctly
+ expectedAuthHeader := fmt.Sprintf("Bearer %s", c.AccessToken)
+ if c.AuthHeader != expectedAuthHeader {
+ t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
+ }
+}
diff --git a/auth_providers/keycloak/models.go b/auth_providers/keycloak/models.go
index c5aa986..bf87729 100644
--- a/auth_providers/keycloak/models.go
+++ b/auth_providers/keycloak/models.go
@@ -1,10 +1,24 @@
+// Copyright 2024 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 keycloak
import (
"os"
"time"
- "keyfactor_auth/auth_providers"
+ "keyfactor_auth_client/auth_providers"
)
const (
diff --git a/auth_providers/models.go b/auth_providers/models.go
index 7659d8e..f1d27d7 100644
--- a/auth_providers/models.go
+++ b/auth_providers/models.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 (
diff --git a/go.mod b/go.mod
index bbe8ccd..557ec42 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,17 @@
-module keyfactor_auth
+// Copyright 2024 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.
+
+module keyfactor_auth_client
go 1.22
diff --git a/main.go b/main.go
index e36cb7b..865aef8 100644
--- a/main.go
+++ b/main.go
@@ -1,21 +1,27 @@
+// Copyright 2024 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 main
import (
- "log"
+ "fmt"
- "keyfactor_auth/auth_providers/keycloak"
+ "keyfactor_auth_client/pkg"
)
func main() {
- //try client auth
- keyfactorAuthConfig := keycloak.CommandAuthKeyCloakClientCredentials{}
- aErr := keyfactorAuthConfig.Authenticate()
- if aErr != nil {
- log.Fatalf("[ERROR] %s\n", aErr)
- }
- log.Println("[INFO] Successfully authenticated with Keyfactor")
- log.Println("[INFO] Token: ", keyfactorAuthConfig.AccessToken)
- log.Println("[INFO] Refresh Token: ", keyfactorAuthConfig.RefreshToken)
- log.Println("[INFO] Token Expiry: ", keyfactorAuthConfig.Expiry)
-
+ fmt.Println("Version:", pkg.Version) // print the package version
+ fmt.Println("Build:", pkg.BuildTime) // print the package build
+ fmt.Println("Commit:", pkg.CommitHash) // print the package commit
}
diff --git a/pkg/version.go b/pkg/version.go
new file mode 100644
index 0000000..25ba0e1
--- /dev/null
+++ b/pkg/version.go
@@ -0,0 +1,7 @@
+package pkg
+
+var (
+ Version string
+ BuildTime string
+ CommitHash string
+)
diff --git a/scripts/auth_keycloak.ps1 b/scripts/auth_keycloak.ps1
index 3e97c9c..e2f889d 100644
--- a/scripts/auth_keycloak.ps1
+++ b/scripts/auth_keycloak.ps1
@@ -1,3 +1,17 @@
+# Copyright 2024 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.
+
function CheckVars {
# Check hostname
if (!$env:KEYFACTOR_AUTH_HOSTNAME) {
diff --git a/scripts/auth_keycloak.sh b/scripts/auth_keycloak.sh
index b01f7a4..0d55a0a 100755
--- a/scripts/auth_keycloak.sh
+++ b/scripts/auth_keycloak.sh
@@ -1,5 +1,19 @@
#!/usr/bin/env bash
+# Copyright 2024 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.
+
# Input vars
function checkVars() {
# Check hostname
From edeeff1b40cea66f87ab471e331c075efa909198 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 6 May 2024 08:51:35 -0700
Subject: [PATCH 04/57] feat(core): `CommandAuthConfig` now has an
`Authenticate` receiver that tests credentials against the `Status/Endpoints`
API and sets product version.
---
README.md | 2 +-
auth_providers/auth_core.go | 80 +++++++++++++++++++
.../keycloak_auth_client_credentials.go | 5 ++
.../keycloak_auth_client_credentials_test.go | 36 ++++-----
auth_providers/models.go | 4 +
5 files changed, 108 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index de9d97d..4bab47d 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,8 @@ Client library for authenticating to Keyfactor Command
|--------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------|
| KEYFACTOR_AUTH_HOST_NAME | Hostname of Keycloak instance to authenticate to Keyfactor Command | |
| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
+| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
| KEYFACTOR_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | |
-| KEYFACTOR_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
#### Client Credentials
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 32aeaf4..040a5cf 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -15,8 +15,12 @@
package auth_providers
import (
+ "encoding/json"
"fmt"
+ "io"
+ "net/http"
"os"
+ "time"
)
func (c *CommandAuthConfig) ValidateAuthConfig() error {
@@ -43,3 +47,79 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
}
return nil
}
+
+func (c *CommandAuthConfig) Authenticate() error {
+ // call /Status/Endpoints API to validate credentials
+
+ //create headers for request
+ headers := map[string]string{
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ "Authorization": c.AuthHeader,
+ "x-keyfactor-api-version": DefaultAPIVersion,
+ "x-keyfactor-requested-with": DefaultAPIClientName,
+ }
+
+ endPoint := fmt.Sprintf(
+ "https://%s/%s/Status/Endpoints",
+ c.CommandHostName,
+ //c.CommandPort,
+ c.CommandAPIPath,
+ )
+
+ // create request object
+ req, rErr := http.NewRequest("GET", endPoint, nil)
+ if rErr != nil {
+ return rErr
+ }
+ // Set headers from the map
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ c.HttpClient.Timeout = 60 * time.Second
+
+ cResp, cErr := c.HttpClient.Do(req)
+ if cErr != nil {
+ return cErr
+ } else if cResp == nil {
+ return fmt.Errorf("failed to authenticate, no response received from Keyfactor Command")
+ }
+
+ defer cResp.Body.Close()
+
+ // check if body is empty
+ if cResp.Body == nil {
+ return fmt.Errorf("failed to authenticate, empty response body received from Keyfactor Command")
+ }
+
+ cRespBody, ioErr := io.ReadAll(cResp.Body)
+ if ioErr != nil {
+ return ioErr
+ }
+
+ if cResp.StatusCode != 200 {
+ //convert body to string
+ return fmt.Errorf(
+ "failed to authenticate, received status code %d from Keyfactor Command: %s",
+ cResp.StatusCode,
+ string(cRespBody),
+ )
+ }
+
+ productVersion := cResp.Header.Get("x-keyfactor-product-version")
+ if productVersion != "" {
+ c.CommandVersion = productVersion
+ } else {
+ c.CommandVersion = DefaultProductVersion
+ }
+
+ //decode response to json
+ var response []string
+ if err := json.Unmarshal(cRespBody, &response); err != nil {
+ return err
+ }
+
+ return nil
+
+}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index 15ae01a..8cdbcb8 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -52,6 +52,11 @@ func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
c.AuthHeader = fmt.Sprintf("Bearer %s", token)
+ aErr := c.CommandAuthConfig.Authenticate()
+ if aErr != nil {
+ return aErr
+ }
+
return nil
}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
index 0546d3a..70715ac 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
@@ -34,25 +34,25 @@ func TestCommandAuthKeyCloakClientCredentials_AuthenticateMocked(t *testing.T) {
defer ts.Close()
// Create a new CommandAuthKeyCloakClientCredentials instance
- c := &CommandAuthKeyCloakClientCredentials{
- ClientID: "test_client_id",
- ClientSecret: "test_client_secret",
- Realm: "test_realm",
- TokenURL: ts.URL, // Use the URL of the test server
- }
-
- // Call the Authenticate method
- err := c.Authenticate()
- if err != nil {
- t.Errorf("Authenticate() error = %v", err)
- return
- }
+ //c := &CommandAuthKeyCloakClientCredentials{
+ // ClientID: "test_client_id",
+ // ClientSecret: "test_client_secret",
+ // Realm: "test_realm",
+ // TokenURL: ts.URL, // Use the URL of the test server
+ //}
- // Check that the AuthHeader was set correctly
- expectedAuthHeader := "Bearer test_token"
- if c.AuthHeader != expectedAuthHeader {
- t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
- }
+ //// Call the Authenticate method
+ //err := c.Authenticate()
+ //if err != nil {
+ // t.Errorf("Authenticate() error = %v", err)
+ // return
+ //}
+ //
+ //// Check that the AuthHeader was set correctly
+ //expectedAuthHeader := "Bearer test_token"
+ //if c.AuthHeader != expectedAuthHeader {
+ // t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
+ //}
}
func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
diff --git a/auth_providers/models.go b/auth_providers/models.go
index f1d27d7..65684b0 100644
--- a/auth_providers/models.go
+++ b/auth_providers/models.go
@@ -24,12 +24,16 @@ type CommandAuthConfig struct {
CommandHostName string `json:"command_host_name"`
CommandPort string `json:"command_port"`
CommandAPIPath string `json:"command_api_path"`
+ CommandVersion string `json:"command_version"`
HttpClient *http.Client
}
const (
DefaultCommandPort = "443"
DefaultCommandAPIPath = "KeyfactorAPI"
+ DefaultAPIVersion = "1"
+ DefaultAPIClientName = "APIClient"
+ DefaultProductVersion = "10.5.0.0"
EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
EnvKeyfactorPort = "KEYFACTOR_PORT"
EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
From 59c8e9e60d491f98ec84b7a43f7db59cdb5ef04e Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 6 May 2024 09:49:23 -0700
Subject: [PATCH 05/57] fix(ad): AD auth parses domain from username properly.
fix(core): Init http client on auth if it's not initialized.
---
auth_providers/active_directory/ad_auth.go | 59 ++++++++++++-------
.../active_directory/ad_auth_test.go | 39 ++++++++++++
auth_providers/auth_core.go | 4 ++
.../keycloak_auth_client_credentials.go | 56 ++++++++++++++----
auth_providers/keycloak/models.go | 1 -
5 files changed, 124 insertions(+), 35 deletions(-)
create mode 100644 auth_providers/active_directory/ad_auth_test.go
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
index 48c4d66..e570a70 100644
--- a/auth_providers/active_directory/ad_auth.go
+++ b/auth_providers/active_directory/ad_auth.go
@@ -28,14 +28,41 @@ func (c *CommandAuthConfigActiveDirectory) Authenticate() error {
}
c.AuthHeader = fmt.Sprintf("Basic %s", c.getBasicAuthHeader())
+
+ aErr := c.CommandAuthConfigBasic.Authenticate()
+ if aErr != nil {
+ return aErr
+ }
+
return nil
}
func (c *CommandAuthConfigActiveDirectory) getBasicAuthHeader() string {
- authStr := fmt.Sprintf("%s@%s:%s", c.Domain, c.Username, c.Password)
+ authStr := fmt.Sprintf("%s@%s:%s", c.Username, c.Domain, c.Password)
return base64.StdEncoding.EncodeToString([]byte(authStr))
}
+func (c *CommandAuthConfigActiveDirectory) parseUsernameDomain() error {
+ domainErr := fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
+ if strings.Contains(c.Username, "@") {
+ dSplit := strings.Split(c.Username, "@")
+ if len(dSplit) != 2 {
+ return domainErr
+ }
+ c.Username = dSplit[0] // remove domain from username
+ c.Domain = dSplit[1]
+ } else if strings.Contains(c.Username, "\\") {
+ dSplit := strings.Split(c.Username, "\\")
+ if len(dSplit) != 2 {
+ return domainErr
+ }
+ c.Domain = dSplit[0]
+ c.Username = dSplit[1] // remove domain from username
+ }
+
+ return nil
+}
+
func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
cErr := c.CommandAuthConfigBasic.ValidateAuthConfig()
if cErr != nil {
@@ -50,38 +77,28 @@ func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
}
}
+ domainErr := c.parseUsernameDomain()
+ if domainErr != nil {
+ return domainErr
+
+ }
+
if c.Password == "" {
if password, ok := os.LookupEnv(EnvKeyfactorPassword); ok {
c.Password = password
} else {
- return fmt.Errorf("password or environment variable KEYFACTOR_PASSWORD is required")
+ return fmt.Errorf("password or environment variable %s is required", EnvKeyfactorPassword)
}
}
if c.Domain == "" {
- if domain, ok := os.LookupEnv("KEYFACTOR_DOMAIN"); ok {
+ if domain, ok := os.LookupEnv(EnvKeyfactorDomain); ok {
c.Domain = domain
} else {
- //check if domain is in username with @ or \\
- if strings.Contains(c.Username, "@") {
- domain := strings.Split(c.Username, "@")
- if len(domain) != 2 {
- return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
- }
- c.Username = domain[0] // remove domain from username
- c.Domain = domain[1]
- } else if strings.Contains(c.Username, "\\") {
- domain := strings.Split(c.Username, "\\")
- if len(domain) != 2 {
- return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
- }
- c.Domain = domain[0]
- c.Username = domain[1] // remove domain from username
- } else {
- return fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
- }
+ return domainErr
}
}
+
return nil
}
diff --git a/auth_providers/active_directory/ad_auth_test.go b/auth_providers/active_directory/ad_auth_test.go
new file mode 100644
index 0000000..85b4f29
--- /dev/null
+++ b/auth_providers/active_directory/ad_auth_test.go
@@ -0,0 +1,39 @@
+// Copyright 2024 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 active_directory
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestCommandAuthActiveDirectoryCredentials_AuthEnvironment(t *testing.T) {
+
+ // Create a new CommandAuthActiveDirectoryCredentials instance
+ c := &CommandAuthConfigActiveDirectory{} //Used environment configuration
+
+ // Call the Authenticate method
+ err := c.Authenticate()
+ if err != nil {
+ t.Errorf("Authenticate() error = %v", err)
+ return
+ }
+
+ // Check that the AuthHeader was set correctly
+ expectedAuthHeader := fmt.Sprintf("Basic %s", c.getBasicAuthHeader())
+ if c.AuthHeader != expectedAuthHeader {
+ t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
+ }
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 040a5cf..af35fcd 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -51,6 +51,10 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
func (c *CommandAuthConfig) Authenticate() error {
// call /Status/Endpoints API to validate credentials
+ if c.HttpClient == nil {
+ c.HttpClient = &http.Client{}
+ }
+
//create headers for request
headers := map[string]string{
"Content-Type": "application/json",
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index 8cdbcb8..f3257ed 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -35,7 +35,6 @@ const (
)
func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
- c.HttpClient = &http.Client{}
c.AuthType = "client_credentials"
cErr := c.ValidateAuthConfig()
if cErr != nil {
@@ -60,12 +59,7 @@ func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
return nil
}
-func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
- cErr := c.CommandAuthConfigKeyCloak.ValidateAuthConfig()
- if cErr != nil {
- return cErr
- }
-
+func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
if c.ClientID == "" {
if clientID, ok := os.LookupEnv(EnvKeyfactorClientID); ok {
c.ClientID = clientID
@@ -73,6 +67,10 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
return fmt.Errorf("client_id or environment variable %s is required", EnvKeyfactorClientID)
}
}
+ return nil
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) setClientSecret() error {
if c.ClientSecret == "" {
if clientSecret, ok := os.LookupEnv(EnvKeyfactorClientSecret); ok {
c.ClientSecret = clientSecret
@@ -80,6 +78,10 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
return fmt.Errorf("client_secret or environment variable %s is required", EnvKeyfactorClientSecret)
}
}
+ return nil
+}
+
+func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
if c.Realm == "" {
if realm, ok := os.LookupEnv(EnvKeyfactorAuthRealm); ok {
c.Realm = realm
@@ -87,22 +89,50 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
c.Realm = DefaultKeyfactorRealm
}
}
+ return nil
+}
+func (c *CommandAuthKeyCloakClientCredentials) setTokenURL() error {
if c.TokenURL == "" {
if tokenURL, ok := os.LookupEnv(EnvKeyfactorAuthTokenURL); ok {
c.TokenURL = tokenURL
} else {
- c.TokenURL = fmt.Sprintf(
- "https://%s:%s/realms/%s/protocol/openid-connect/token",
- c.AuthHostName,
- c.AuthPort,
- c.Realm,
- )
+ return fmt.Errorf("token_url or environment variable %s is required", EnvKeyfactorAuthTokenURL)
}
}
return nil
}
+func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
+ cErr := c.CommandAuthConfigKeyCloak.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+
+ cIdErr := c.setClientId()
+ if cIdErr != nil {
+ return cIdErr
+ }
+
+ cSecretErr := c.setClientSecret()
+ if cSecretErr != nil {
+ return cSecretErr
+ }
+
+ rErr := c.setRealm()
+ if rErr != nil {
+ return rErr
+ }
+
+ tErr := c.setTokenURL()
+ if tErr != nil {
+ return tErr
+
+ }
+
+ return nil
+}
+
func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
if c.AccessToken != "" && time.Now().Before(c.Expiry) {
return c.AccessToken, nil
diff --git a/auth_providers/keycloak/models.go b/auth_providers/keycloak/models.go
index bf87729..1cd0dd4 100644
--- a/auth_providers/keycloak/models.go
+++ b/auth_providers/keycloak/models.go
@@ -55,7 +55,6 @@ func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
}
}
return nil
-
}
type CommandAuthKeyCloakClientCredentials struct {
From b91fe09378e249ec3d83c70a926ab0556966af4d Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 10:27:38 -0700
Subject: [PATCH 06/57] fix(core): Core client to set http client if base
config is valid. fix(keycloak): Client credentials implicitly construct token
endpoint URL based on hostname, realm and port inputs if not set explicitly
in environment. chore(tests): Update go tests to accommodate different auth
environment types. chore(ci): Add keyfactor and dependabot GitHub workflows.
chore(docs): Add godoc strings
---
.github/dependabot.yml | 12 ++++
.../workflows/keyfactor-starter-workflow.yml | 20 ++++++
README.md | 10 +++
auth_config_schema.yml | 3 +
auth_providers/active_directory/ad_auth.go | 30 ++++++--
.../active_directory/ad_auth_test.go | 18 +++++
.../models.go => auth_basic.go} | 21 +++---
auth_providers/auth_core.go | 53 +++++++++++++-
...models.go => keycloak_auth_client_base.go} | 25 +++----
.../keycloak_auth_client_credentials.go | 70 ++++++++++++++++++-
.../keycloak_auth_client_credentials_test.go | 53 +++++---------
auth_providers/models.go | 46 ------------
12 files changed, 244 insertions(+), 117 deletions(-)
create mode 100644 .github/dependabot.yml
create mode 100644 .github/workflows/keyfactor-starter-workflow.yml
create mode 100644 auth_config_schema.yml
rename auth_providers/{active_directory/models.go => auth_basic.go} (52%)
rename auth_providers/keycloak/{models.go => keycloak_auth_client_base.go} (70%)
delete mode 100644 auth_providers/models.go
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..fa3ed22
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# See GitHub's documentation for more information on this file:
+# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "daily"
\ No newline at end of file
diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml
new file mode 100644
index 0000000..97011fc
--- /dev/null
+++ b/.github/workflows/keyfactor-starter-workflow.yml
@@ -0,0 +1,20 @@
+name: Keyfactor Bootstrap Workflow
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [ opened, closed, synchronize, edited, reopened ]
+ push:
+ create:
+ branches:
+ - 'release-*.*'
+
+jobs:
+ call-starter-workflow:
+ uses: keyfactor/actions/.github/workflows/starter.yml@v2
+ needs: get-versions
+ secrets:
+ token: ${{ secrets.V2BUILDTOKEN}}
+ APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
+ gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
+ gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
\ No newline at end of file
diff --git a/README.md b/README.md
index 4bab47d..41535f3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# keyfactor-auth-client-go
+
Client library for authenticating to Keyfactor Command
@@ -8,6 +9,7 @@ Client library for authenticating to Keyfactor Command
* [Active Directory](#active-directory)
* [Keycloak](#keycloak)
+ [Client Credentials](#client-credentials)
+- [Test Environment Variables](#test-environment-variables)
@@ -44,3 +46,11 @@ Client library for authenticating to Keyfactor Command
|------------------------------|------------------------------|---------|
| KEYFACTOR_AUTH_CLIENT_ID | Keyfactor Auth Client ID | |
| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
+
+## 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 |
\ No newline at end of file
diff --git a/auth_config_schema.yml b/auth_config_schema.yml
new file mode 100644
index 0000000..f1c5a1e
--- /dev/null
+++ b/auth_config_schema.yml
@@ -0,0 +1,3 @@
+---
+servers:
+ description: The list of servers to authenticate against
\ No newline at end of file
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
index e570a70..42fe0a1 100644
--- a/auth_providers/active_directory/ad_auth.go
+++ b/auth_providers/active_directory/ad_auth.go
@@ -19,8 +19,28 @@ import (
"fmt"
"os"
"strings"
+
+ "keyfactor_auth_client/auth_providers"
+)
+
+const (
+ EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
+ EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
+ EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
)
+// CommandAuthConfigActiveDirectory represents the configuration needed for Active Directory authentication.
+// It embeds CommandAuthConfigBasic and adds additional fields specific to Active Directory.
+type CommandAuthConfigActiveDirectory struct {
+ // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
+ auth_providers.CommandAuthConfigBasic
+
+ // Domain is the domain of the Active Directory used to authenticate to Keyfactor Command API
+ Domain string `json:"domain"`
+}
+
+// Authenticate performs the authentication process for Active Directory.
+// It validates the authentication configuration, generates the authentication header, and calls the basic authentication method.
func (c *CommandAuthConfigActiveDirectory) Authenticate() error {
cErr := c.ValidateAuthConfig()
if cErr != nil {
@@ -37,11 +57,15 @@ func (c *CommandAuthConfigActiveDirectory) Authenticate() error {
return nil
}
+// getBasicAuthHeader generates the basic authentication header value.
+// It combines the username, domain, and password with a colon separator, and encodes the resulting string in base64.
func (c *CommandAuthConfigActiveDirectory) getBasicAuthHeader() string {
authStr := fmt.Sprintf("%s@%s:%s", c.Username, c.Domain, c.Password)
return base64.StdEncoding.EncodeToString([]byte(authStr))
}
+// parseUsernameDomain parses the username to extract the domain if it's included in the username.
+// It supports two formats: "username@domain" and "domain\username".
func (c *CommandAuthConfigActiveDirectory) parseUsernameDomain() error {
domainErr := fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
if strings.Contains(c.Username, "@") {
@@ -63,6 +87,8 @@ func (c *CommandAuthConfigActiveDirectory) parseUsernameDomain() error {
return nil
}
+// ValidateAuthConfig validates the authentication configuration for Active Directory.
+// It checks the username, domain, and password, and retrieves them from environment variables if they're not set.
func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
cErr := c.CommandAuthConfigBasic.ValidateAuthConfig()
if cErr != nil {
@@ -101,7 +127,3 @@ func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
return nil
}
-
-func (c *CommandAuthConfigActiveDirectory) GetAuthHeader() string {
- return c.AuthHeader
-}
diff --git a/auth_providers/active_directory/ad_auth_test.go b/auth_providers/active_directory/ad_auth_test.go
index 85b4f29..98d94a7 100644
--- a/auth_providers/active_directory/ad_auth_test.go
+++ b/auth_providers/active_directory/ad_auth_test.go
@@ -16,11 +16,22 @@ package active_directory
import (
"fmt"
+ "os"
"testing"
)
+const (
+ TestEnvIsADAuth = "TEST_KEYFACTOR_AD_AUTH"
+)
+
func TestCommandAuthActiveDirectoryCredentials_AuthEnvironment(t *testing.T) {
+ if !checkAuthEnvADCreds() {
+ msg := "Skipping test because Keyfactor Command environment is not authenticated with Active Directory credentials"
+ t.Log(msg)
+ t.Skip(msg)
+ return
+ }
// Create a new CommandAuthActiveDirectoryCredentials instance
c := &CommandAuthConfigActiveDirectory{} //Used environment configuration
@@ -37,3 +48,10 @@ func TestCommandAuthActiveDirectoryCredentials_AuthEnvironment(t *testing.T) {
t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
}
}
+
+func checkAuthEnvADCreds() bool {
+ if _, ok := os.LookupEnv(TestEnvIsADAuth); ok {
+ return true
+ }
+ return false
+}
diff --git a/auth_providers/active_directory/models.go b/auth_providers/auth_basic.go
similarity index 52%
rename from auth_providers/active_directory/models.go
rename to auth_providers/auth_basic.go
index e6d71d8..0b2f5d3 100644
--- a/auth_providers/active_directory/models.go
+++ b/auth_providers/auth_basic.go
@@ -12,19 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package active_directory
+package auth_providers
-import (
- "keyfactor_auth_client/auth_providers"
-)
+// CommandAuthConfigBasic represents the base configuration needed for authentication to Keyfactor Command API.
+type CommandAuthConfigBasic struct {
+ // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
+ CommandAuthConfig
-const (
- EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
- EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
- EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
-)
+ // Username is the username to be used for authentication to Keyfactor Command API
+ Username string `json:"username"`
-type CommandAuthConfigActiveDirectory struct {
- auth_providers.CommandAuthConfigBasic
- Domain string `json:"domain"`
+ // Password is the password to be used for authentication to Keyfactor Command API
+ Password string `json:"password"`
}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index af35fcd..ee81da5 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -23,6 +23,45 @@ import (
"time"
)
+const (
+ DefaultCommandPort = "443"
+ DefaultCommandAPIPath = "KeyfactorAPI"
+ DefaultAPIVersion = "1"
+ DefaultAPIClientName = "APIClient"
+ DefaultProductVersion = "10.5.0.0"
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+)
+
+// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
+type CommandAuthConfig struct {
+ // ConfigType is the type of configuration
+ ConfigType string `json:"config_type"`
+
+ // AuthHeader is the header to be used for authentication to Keyfactor Command API
+ AuthHeader string `json:"auth_header"`
+
+ // CommandHostName is the hostname of the Keyfactor Command API
+ CommandHostName string `json:"command_host_name"`
+
+ // CommandPort is the port of the Keyfactor Command API
+ CommandPort string `json:"command_port"`
+
+ // CommandAPIPath is the path of the Keyfactor Command API, default is "KeyfactorAPI"
+ CommandAPIPath string `json:"command_api_path"`
+
+ // CommandAPIVersion is the version of the Keyfactor Command API, default is "1"
+ CommandVersion string `json:"command_version"`
+
+ // CommandCACert is the CA certificate to be used for authentication to Keyfactor Command API for use with not widely trusted certificates
+ CommandCACert string `json:"command_ca_cert;omitempty"`
+
+ // HttpClient is the http client to be used for authentication to Keyfactor Command API
+ HttpClient *http.Client
+}
+
+// ValidateAuthConfig validates the authentication configuration for Keyfactor Command API.
func (c *CommandAuthConfig) ValidateAuthConfig() error {
if c.CommandHostName == "" {
if hostName, ok := os.LookupEnv(EnvKeyfactorHostName); ok {
@@ -45,15 +84,23 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.CommandAPIPath = DefaultCommandAPIPath
}
}
+
+ c.setClient()
+
return nil
}
-func (c *CommandAuthConfig) Authenticate() error {
- // call /Status/Endpoints API to validate credentials
-
+// setClient sets the http client for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) setClient() {
if c.HttpClient == nil {
c.HttpClient = &http.Client{}
}
+}
+
+// Authenticate performs the authentication test to Keyfactor Command API and sets Command product version.
+func (c *CommandAuthConfig) Authenticate() error {
+ // call /Status/Endpoints API to validate credentials
+ c.setClient()
//create headers for request
headers := map[string]string{
diff --git a/auth_providers/keycloak/models.go b/auth_providers/keycloak/keycloak_auth_client_base.go
similarity index 70%
rename from auth_providers/keycloak/models.go
rename to auth_providers/keycloak/keycloak_auth_client_base.go
index 1cd0dd4..411b40f 100644
--- a/auth_providers/keycloak/models.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -16,7 +16,6 @@ package keycloak
import (
"os"
- "time"
"keyfactor_auth_client/auth_providers"
)
@@ -24,16 +23,23 @@ import (
const (
EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOST_NAME"
EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
- EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
)
type CommandAuthConfigKeyCloak struct {
+ // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
auth_providers.CommandAuthConfig
+
+ // AuthHostName is the hostname of the Keycloak server
AuthHostName string `json:"auth_host_name"`
- AuthPort string `json:"auth_port"`
- AuthType string `json:"auth_type"` // The type of Keycloak auth to use such as client_credentials, password, etc.
+
+ // AuthPort is the port of the Keycloak server
+ AuthPort string `json:"auth_port"`
+
+ // AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
+ AuthType string `json:"auth_type"`
}
+// ValidateAuthConfig validates the authentication configuration for Keycloak.
func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
pErr := c.CommandAuthConfig.ValidateAuthConfig()
if pErr != nil {
@@ -56,14 +62,3 @@ func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
}
return nil
}
-
-type CommandAuthKeyCloakClientCredentials struct {
- CommandAuthConfigKeyCloak
- ClientID string `json:"client_id"`
- ClientSecret string `json:"client_secret"`
- AccessToken string `json:"access_token"`
- RefreshToken string `json:"refresh_token"`
- Expiry time.Time `json:"expiry"`
- Realm string `json:"realm"`
- TokenURL string `json:"token_url"`
-}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index f3257ed..4c21838 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -32,11 +32,47 @@ const (
EnvKeyfactorClientSecret = "KEYFACTOR_CLIENT_SECRET"
EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
+ EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
)
+// CommandAuthKeyCloakClientCredentials represents the configuration needed for Keycloak authentication using client credentials.
+// It embeds CommandAuthConfigKeyCloak and adds additional fields specific to Keycloak client credentials authentication.
+type CommandAuthKeyCloakClientCredentials struct {
+ // CommandAuthConfigKeyCloak is a reference to the base configuration needed for authentication to Keyfactor Command API
+ CommandAuthConfigKeyCloak
+
+ // ClientID is the client ID for Keycloak authentication
+ ClientID string `json:"client_id;omitempty"`
+
+ // ClientSecret is the client secret for Keycloak authentication
+ ClientSecret string `json:"client_secret;omitempty"`
+
+ // AccessToken is the access token for Keycloak authentication
+ AccessToken string `json:"access_token;omitempty"`
+
+ // RefreshToken is the refresh token for Keycloak authentication
+ RefreshToken string `json:"refresh_token;omitempty"`
+
+ // Expiry is the expiry time of the access token
+ Expiry time.Time `json:"expiry;omitempty"`
+
+ // Realm is the realm for Keycloak authentication
+ Realm string `json:"realm;omitempty"`
+
+ // TokenURL is the token URL for Keycloak authentication
+ TokenURL string `json:"token_url"`
+}
+
+// Authenticate performs the authentication process for Keycloak using client credentials.
+// It validates the authentication configuration, gets the token, and calls the base authentication method.
func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
+ cErr := c.CommandAuthConfig.ValidateAuthConfig() // Validate base config
+ if cErr != nil {
+ return cErr
+ }
+
c.AuthType = "client_credentials"
- cErr := c.ValidateAuthConfig()
+ cErr = c.ValidateAuthConfig()
if cErr != nil {
return cErr
}
@@ -59,6 +95,8 @@ func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
return nil
}
+// setClientId sets the client ID for Keycloak authentication.
+// It retrieves the client ID from environment variables if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
if c.ClientID == "" {
if clientID, ok := os.LookupEnv(EnvKeyfactorClientID); ok {
@@ -70,6 +108,8 @@ func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
return nil
}
+// setClientSecret sets the client secret for Keycloak authentication.
+// It retrieves the client secret from environment variables if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setClientSecret() error {
if c.ClientSecret == "" {
if clientSecret, ok := os.LookupEnv(EnvKeyfactorClientSecret); ok {
@@ -81,6 +121,8 @@ func (c *CommandAuthKeyCloakClientCredentials) setClientSecret() error {
return nil
}
+// setRealm sets the realm for Keycloak authentication.
+// It retrieves the realm from environment variables if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
if c.Realm == "" {
if realm, ok := os.LookupEnv(EnvKeyfactorAuthRealm); ok {
@@ -92,17 +134,26 @@ func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
return nil
}
+// setTokenURL sets the token URL for Keycloak authentication.
+// It generates the token URL if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setTokenURL() error {
if c.TokenURL == "" {
if tokenURL, ok := os.LookupEnv(EnvKeyfactorAuthTokenURL); ok {
c.TokenURL = tokenURL
} else {
- return fmt.Errorf("token_url or environment variable %s is required", EnvKeyfactorAuthTokenURL)
+ c.TokenURL = fmt.Sprintf(
+ "https://%s:%s/realms/%s/protocol/openid-connect/token",
+ c.AuthHostName,
+ c.AuthPort,
+ c.Realm,
+ )
}
}
return nil
}
+// ValidateAuthConfig validates the authentication configuration for Keycloak using client credentials.
+// It checks the client ID, client secret, realm, and token URL, and retrieves them from environment variables if they're not set.
func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
cErr := c.CommandAuthConfigKeyCloak.ValidateAuthConfig()
if cErr != nil {
@@ -133,7 +184,19 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
return nil
}
+// GetToken gets the access token for Keycloak authentication.
+// It uses the refresh token if available and not expired, otherwise, it requests a new access token.
func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
+ // Check if access token is set in environment variable
+ if c.AccessToken == "" {
+ if accessToken, ok := os.LookupEnv(EnvKeyfactorAccessToken); ok {
+ c.AccessToken = accessToken
+
+ // Don't try to refresh as we don't have a refresh token
+ return c.AccessToken, nil
+ }
+ }
+
if c.AccessToken != "" && time.Now().Before(c.Expiry) {
return c.AccessToken, nil
}
@@ -147,6 +210,7 @@ func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
return c.requestNewToken()
}
+// requestNewToken requests a new access token for Keycloak authentication using client credentials.
func (c *CommandAuthKeyCloakClientCredentials) requestNewToken() (string, error) {
formData := url.Values{}
formData.Set("grant_type", "client_credentials")
@@ -156,6 +220,7 @@ func (c *CommandAuthKeyCloakClientCredentials) requestNewToken() (string, error)
return c.doTokenRequest(formData.Encode())
}
+// refreshAccessToken refreshes the access token for Keycloak authentication.
func (c *CommandAuthKeyCloakClientCredentials) refreshAccessToken() (string, error) {
formData := url.Values{}
formData.Set("grant_type", "refresh_token")
@@ -165,6 +230,7 @@ func (c *CommandAuthKeyCloakClientCredentials) refreshAccessToken() (string, err
return c.doTokenRequest(formData.Encode())
}
+// doTokenRequest sends a token request to Keycloak and handles the response.
func (c *CommandAuthKeyCloakClientCredentials) doTokenRequest(data string) (string, error) {
requestBody := strings.NewReader(data)
req, reqErr := http.NewRequest("POST", c.TokenURL, requestBody)
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
index 70715ac..b43168a 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
@@ -16,47 +16,23 @@ package keycloak
import (
"fmt"
- "net/http"
- "net/http/httptest"
+ "os"
"testing"
)
-func TestCommandAuthKeyCloakClientCredentials_AuthenticateMocked(t *testing.T) {
- // Create a test server that returns a 200 status and a token
- ts := httptest.NewServer(
- http.HandlerFunc(
- func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"access_token": "test_token", "expires_in": 3600}`))
- },
- ),
- )
- defer ts.Close()
-
- // Create a new CommandAuthKeyCloakClientCredentials instance
- //c := &CommandAuthKeyCloakClientCredentials{
- // ClientID: "test_client_id",
- // ClientSecret: "test_client_secret",
- // Realm: "test_realm",
- // TokenURL: ts.URL, // Use the URL of the test server
- //}
-
- //// Call the Authenticate method
- //err := c.Authenticate()
- //if err != nil {
- // t.Errorf("Authenticate() error = %v", err)
- // return
- //}
- //
- //// Check that the AuthHeader was set correctly
- //expectedAuthHeader := "Bearer test_token"
- //if c.AuthHeader != expectedAuthHeader {
- // t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
- //}
-}
+const (
+ TestEnvIsClientAuth = "TEST_KEYFACTOR_KC_AUTH"
+)
func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
+ if !checkAuthEnvClientCreds() {
+ msg := "Skipping test because Keyfactor Command environment is not authenticated with client credentials"
+ t.Log(msg)
+ t.Skip(msg)
+ return
+ }
+
// Create a new CommandAuthKeyCloakClientCredentials instance
c := &CommandAuthKeyCloakClientCredentials{} //Used environment configuration
@@ -73,3 +49,10 @@ func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
}
}
+
+func checkAuthEnvClientCreds() bool {
+ if _, ok := os.LookupEnv(TestEnvIsClientAuth); ok {
+ return true
+ }
+ return false
+}
diff --git a/auth_providers/models.go b/auth_providers/models.go
deleted file mode 100644
index 65684b0..0000000
--- a/auth_providers/models.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2024 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 (
- "net/http"
-)
-
-type CommandAuthConfig struct {
- ConfigType string `json:"config_type"`
- AuthHeader string `json:"auth_header"`
- CommandHostName string `json:"command_host_name"`
- CommandPort string `json:"command_port"`
- CommandAPIPath string `json:"command_api_path"`
- CommandVersion string `json:"command_version"`
- HttpClient *http.Client
-}
-
-const (
- DefaultCommandPort = "443"
- DefaultCommandAPIPath = "KeyfactorAPI"
- DefaultAPIVersion = "1"
- DefaultAPIClientName = "APIClient"
- DefaultProductVersion = "10.5.0.0"
- EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
- EnvKeyfactorPort = "KEYFACTOR_PORT"
- EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
-)
-
-type CommandAuthConfigBasic struct {
- CommandAuthConfig
- Username string `json:"username"`
- Password string `json:"password"`
-}
From 2ae54bf65a53971862b5ff4dc8683f5a7f90cf45 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 14:20:49 -0700
Subject: [PATCH 07/57] chore(ci): Adding lab tests
---
.github/config/environments.tf | 31 +++++++++++++++
.github/config/providers.tf | 20 ++++++++++
.github/config/repo.tf | 3 ++
.github/config/variables.tf | 38 +++++++++++++++++++
.github/workflows/go_tests.yml | 32 ++++++++++++++++
README.md | 21 +++++-----
.../active_directory/ad_auth_test.go | 6 ++-
.../keycloak/keycloak_auth_client_base.go | 2 +-
.../keycloak_auth_client_credentials_test.go | 6 ++-
9 files changed, 144 insertions(+), 15 deletions(-)
create mode 100644 .github/config/environments.tf
create mode 100644 .github/config/providers.tf
create mode 100644 .github/config/repo.tf
create mode 100644 .github/config/variables.tf
create mode 100644 .github/workflows/go_tests.yml
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
new file mode 100644
index 0000000..f2631ee
--- /dev/null
+++ b/.github/config/environments.tf
@@ -0,0 +1,31 @@
+module "keyfactor_github_test_environment_ad_10_5_0" {
+ source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git?ref=main"
+
+ gh_environment_name = "KFC_10_5_0"
+ gh_repo_name = data.github_repository.repo.name
+ keyfactor_hostname = var.keyfactor_hostname_10_5_0
+ keyfactor_username = var.keyfactor_username_10_5_0
+ keyfactor_password = var.keyfactor_password_10_5_0
+}
+
+# module "keyfactor_github_test_environment_11_5_0_ad" {
+# source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git?ref=v1.0.0"
+#
+# gh_environment_name = "KFC_11_5_0_AD"
+# gh_repo_name = data.github_repository.repo.name
+# keyfactor_hostname = var.keyfactor_hostname_11_5_0_AD
+# keyfactor_username = var.keyfactor_username_11_5_0_AD
+# keyfactor_password = var.keyfactor_password_11_5_0_AD
+# }
+
+module "keyfactor_github_test_environment_11_5_0_kc" {
+ source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
+
+ gh_environment_name = "KFC_11_5_0_KC"
+ gh_repo_name = data.github_repository.repo.name
+ keyfactor_hostname = var.keyfactor_hostname_11_5_0_KC
+ keyfactor_client_id = var.keyfactor_client_id_11_5_0
+ keyfactor_client_secret = var.keyfactor_client_secret_11_5_0
+ keyfactor_auth_hostname = var.keyfactor_auth_hostname_11_5_0_KC
+}
+
diff --git a/.github/config/providers.tf b/.github/config/providers.tf
new file mode 100644
index 0000000..313bfcd
--- /dev/null
+++ b/.github/config/providers.tf
@@ -0,0 +1,20 @@
+terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ github = {
+ source = "integrations/github"
+ version = ">=6.2"
+ }
+ }
+ backend "azurerm" {
+ resource_group_name = "integrations-infra"
+ storage_account_name = "integrationstfstate"
+ container_name = "tfstate"
+ key = "github/repos/keyfactor-auth-client-go.tfstate"
+ }
+}
+
+provider "github" {
+ # Configuration options
+ owner = "Keyfactor"
+}
\ No newline at end of file
diff --git a/.github/config/repo.tf b/.github/config/repo.tf
new file mode 100644
index 0000000..51e9c49
--- /dev/null
+++ b/.github/config/repo.tf
@@ -0,0 +1,3 @@
+data "github_repository" "repo" {
+ name = "keyfactor-auth-client-go"
+}
\ No newline at end of file
diff --git a/.github/config/variables.tf b/.github/config/variables.tf
new file mode 100644
index 0000000..8c52d88
--- /dev/null
+++ b/.github/config/variables.tf
@@ -0,0 +1,38 @@
+variable "keyfactor_hostname_10_5_0" {
+ description = "The hostname of the Keyfactor instance"
+ type = string
+ default = "integrations1050-lab.kfdelivery.com"
+}
+
+variable "keyfactor_username_10_5_0" {
+ description = "The username to authenticate with the Keyfactor instance"
+ type = string
+}
+
+variable "keyfactor_password_10_5_0" {
+ description = "The password to authenticate with the Keyfactor instance"
+ type = string
+}
+
+variable "keyfactor_client_id_11_5_0" {
+ description = "The client ID to authenticate with the Keyfactor instance using Keycloak client credentials"
+ type = string
+}
+
+variable "keyfactor_client_secret_11_5_0" {
+ description = "The client secret to authenticate with the Keyfactor instance using Keycloak client credentials"
+ type = string
+}
+
+variable "keyfactor_hostname_11_5_0_KC" {
+ description = "The hostname of the Keyfactor instance"
+ type = string
+ default = "pkiaas-spb.eastus2.cloudapp.azure.com"
+}
+
+variable "keyfactor_auth_hostname_11_5_0_KC" {
+ description = "The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token"
+ type = string
+ default = "pkiaas-spb.eastus2.cloudapp.azure.com"
+}
+
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
new file mode 100644
index 0000000..2fce3bc
--- /dev/null
+++ b/.github/workflows/go_tests.yml
@@ -0,0 +1,32 @@
+name: Go Test Workflow
+
+on: [ push ]
+
+jobs:
+ test:
+ name: Run tests
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ environment: [ "KFC_10_5_0", "KFC_11_5_0_KC" ]
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Set up Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.22
+
+ - name: Run tests
+ run: go test -v -cover ./auth_providers/...
+ env:
+ TEST_ENVIRONMENT: ${{ matrix.environment }}
+ KEYFACTOR_PASSWORD: ${{ secrets.KEYFACTOR_PASSWORD }}
+ KEYFACTOR_USERNAME: ${{ secrets.KEYFACTOR_USERNAME }}
+ KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
+ KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
+ KEYFACTOR_HOSTNAME: ${{ env.KEYFACTOR_HOSTNAME }}
+ KEYFACTOR_AUTH_HOSTNAME: ${{ env.KEYFACTOR_AUTH_HOSTNAME }}
+ TEST_KEYFACTOR_AD_AUTH: ${{ env.TEST_KEYFACTOR_AD_AUTH }}
+ TEST_KEYFACTOR_KC_AUTH: ${{ env.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
diff --git a/README.md b/README.md
index 41535f3..8eb30c8 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,10 @@ Client library for authenticating to Keyfactor Command
- [Environment Variables](#environment-variables)
- * [Global](#global)
- * [Active Directory](#active-directory)
- * [Keycloak](#keycloak)
- + [Client Credentials](#client-credentials)
+ * [Global](#global)
+ * [Active Directory](#active-directory)
+ * [Keycloak](#keycloak)
+ + [Client Credentials](#client-credentials)
- [Test Environment Variables](#test-environment-variables)
@@ -33,12 +33,12 @@ Client library for authenticating to Keyfactor Command
### Keycloak
-| Name | Description | Default |
-|--------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------|
-| KEYFACTOR_AUTH_HOST_NAME | Hostname of Keycloak instance to authenticate to Keyfactor Command | |
-| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
-| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
-| KEYFACTOR_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | |
+| Name | Description | Default |
+|-------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------|
+| KEYFACTOR_AUTH_HOSTNAME | Hostname of Keycloak instance to authenticate to Keyfactor Command | |
+| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
+| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
+| KEYFACTOR_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | |
#### Client Credentials
@@ -48,6 +48,7 @@ Client library for authenticating to Keyfactor Command
| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
## Test Environment Variables
+
These environment variables are used to run go tests. They are not used in the actual client library.
| Name | Description | Default |
diff --git a/auth_providers/active_directory/ad_auth_test.go b/auth_providers/active_directory/ad_auth_test.go
index 98d94a7..50d06b5 100644
--- a/auth_providers/active_directory/ad_auth_test.go
+++ b/auth_providers/active_directory/ad_auth_test.go
@@ -50,8 +50,10 @@ func TestCommandAuthActiveDirectoryCredentials_AuthEnvironment(t *testing.T) {
}
func checkAuthEnvADCreds() bool {
- if _, ok := os.LookupEnv(TestEnvIsADAuth); ok {
- return true
+ if isAdAuth, ok := os.LookupEnv(TestEnvIsADAuth); ok {
+ if isAdAuth == "true" || isAdAuth == "1" {
+ return true
+ }
}
return false
}
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index 411b40f..55fd429 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -21,7 +21,7 @@ import (
)
const (
- EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOST_NAME"
+ EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
)
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
index b43168a..ab10428 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
@@ -51,8 +51,10 @@ func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
}
func checkAuthEnvClientCreds() bool {
- if _, ok := os.LookupEnv(TestEnvIsClientAuth); ok {
- return true
+ if isKCAuth, ok := os.LookupEnv(TestEnvIsClientAuth); ok {
+ if isKCAuth == "true" || isKCAuth == "1" {
+ return true
+ }
}
return false
}
From 53a23c9302064826cb15ab880b77e260d669126e Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 14:25:31 -0700
Subject: [PATCH 08/57] chore(ci): Allow manual tests
---
.github/workflows/go_tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 2fce3bc..64dfd7e 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -1,6 +1,6 @@
name: Go Test Workflow
-on: [ push ]
+on: [ push, workflow_dispatch ]
jobs:
test:
From 356c7fb87d0a72c5025f59b7b103357eaca4d6a2 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 14:56:12 -0700
Subject: [PATCH 09/57] chore(ci): Allow manual tests
---
.github/workflows/go_tests.yml | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 64dfd7e..ade9d6d 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -6,9 +6,7 @@ jobs:
test:
name: Run tests
runs-on: ubuntu-latest
- strategy:
- matrix:
- environment: [ "KFC_10_5_0", "KFC_11_5_0_KC" ]
+ environment: "KFC_10_5_0"
steps:
- name: Check out code
uses: actions/checkout@v3
@@ -21,7 +19,6 @@ jobs:
- name: Run tests
run: go test -v -cover ./auth_providers/...
env:
- TEST_ENVIRONMENT: ${{ matrix.environment }}
KEYFACTOR_PASSWORD: ${{ secrets.KEYFACTOR_PASSWORD }}
KEYFACTOR_USERNAME: ${{ secrets.KEYFACTOR_USERNAME }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
From 2e016b4875ae0578305675fdd00a85c31409b761 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 15:04:35 -0700
Subject: [PATCH 10/57] chore(ci): Allow manual tests
---
.github/workflows/go_tests.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index ade9d6d..9d8f7ae 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -23,7 +23,7 @@ jobs:
KEYFACTOR_USERNAME: ${{ secrets.KEYFACTOR_USERNAME }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
- KEYFACTOR_HOSTNAME: ${{ env.KEYFACTOR_HOSTNAME }}
- KEYFACTOR_AUTH_HOSTNAME: ${{ env.KEYFACTOR_AUTH_HOSTNAME }}
- TEST_KEYFACTOR_AD_AUTH: ${{ env.TEST_KEYFACTOR_AD_AUTH }}
- TEST_KEYFACTOR_KC_AUTH: ${{ env.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
+ KEYFACTOR_HOSTNAME: ${{ secrets.KEYFACTOR_HOSTNAME }}
+ KEYFACTOR_AUTH_HOSTNAME: ${{ secrets.KEYFACTOR_AUTH_HOSTNAME }}
+ TEST_KEYFACTOR_AD_AUTH: ${{ secrets.TEST_KEYFACTOR_AD_AUTH }}
+ TEST_KEYFACTOR_KC_AUTH: ${{ secrets.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
From 569c0efb466a287a75cfe4fa40ca173de23a0ac5 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 15:07:00 -0700
Subject: [PATCH 11/57] chore(ci):
---
.github/workflows/go_tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 9d8f7ae..8926fc3 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -1,6 +1,6 @@
name: Go Test Workflow
-on: [ push, workflow_dispatch ]
+on: [ push ]
jobs:
test:
From 44748835644fe7003eb3417e773eec6750ef637e Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 15:08:49 -0700
Subject: [PATCH 12/57] chore(ci): vars
---
.github/workflows/go_tests.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 8926fc3..e4a6a06 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -23,7 +23,7 @@ jobs:
KEYFACTOR_USERNAME: ${{ secrets.KEYFACTOR_USERNAME }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
- KEYFACTOR_HOSTNAME: ${{ secrets.KEYFACTOR_HOSTNAME }}
- KEYFACTOR_AUTH_HOSTNAME: ${{ secrets.KEYFACTOR_AUTH_HOSTNAME }}
- TEST_KEYFACTOR_AD_AUTH: ${{ secrets.TEST_KEYFACTOR_AD_AUTH }}
- TEST_KEYFACTOR_KC_AUTH: ${{ secrets.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
+ KEYFACTOR_HOSTNAME: ${{ vars.KEYFACTOR_HOSTNAME }}
+ KEYFACTOR_AUTH_HOSTNAME: ${{ vars.KEYFACTOR_AUTH_HOSTNAME }}
+ TEST_KEYFACTOR_AD_AUTH: ${{ vars.TEST_KEYFACTOR_AD_AUTH }}
+ TEST_KEYFACTOR_KC_AUTH: ${{ vars.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
From d8389bd9b697da16fe5bd4c1b00d23876c137470 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 15:11:54 -0700
Subject: [PATCH 13/57] chore(ci): multi env
---
.github/workflows/go_tests.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index e4a6a06..a3d10d7 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -6,7 +6,10 @@ jobs:
test:
name: Run tests
runs-on: ubuntu-latest
- environment: "KFC_10_5_0"
+ strategy:
+ matrix:
+ environment: [ "KFC_10_5_0", "KFC_11_5_0_KC"]
+ environment: ${{ matrix.environment }}
steps:
- name: Check out code
uses: actions/checkout@v3
From fa93edb6c6cc218ef616053d3be69ff8765a51df Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 8 May 2024 15:13:51 -0700
Subject: [PATCH 14/57] fix: update env var names for client creds
---
auth_providers/keycloak/keycloak_auth_client_credentials.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index 4c21838..a93601e 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -28,8 +28,8 @@ import (
const (
DefaultKeyfactorAuthPort = "8444"
DefaultKeyfactorRealm = "Keyfactor"
- EnvKeyfactorClientID = "KEYFACTOR_CLIENT_ID"
- EnvKeyfactorClientSecret = "KEYFACTOR_CLIENT_SECRET"
+ EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
+ EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
From 61bc39107246d0a255971130e07d4fb819a9a4da Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 13 May 2024 08:58:07 -0700
Subject: [PATCH 15/57] feat(core): Allow for passing CA certs to config.
feat(core): Allow to skip TLS verification
---
.github/config/environments.tf | 10 ---
auth_providers/auth_core.go | 82 ++++++++++++++++---
.../keycloak/keycloak_auth_client_base.go | 62 ++++++++++++++
3 files changed, 134 insertions(+), 20 deletions(-)
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
index f2631ee..75ffdfb 100644
--- a/.github/config/environments.tf
+++ b/.github/config/environments.tf
@@ -8,16 +8,6 @@ module "keyfactor_github_test_environment_ad_10_5_0" {
keyfactor_password = var.keyfactor_password_10_5_0
}
-# module "keyfactor_github_test_environment_11_5_0_ad" {
-# source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git?ref=v1.0.0"
-#
-# gh_environment_name = "KFC_11_5_0_AD"
-# gh_repo_name = data.github_repository.repo.name
-# keyfactor_hostname = var.keyfactor_hostname_11_5_0_AD
-# keyfactor_username = var.keyfactor_username_11_5_0_AD
-# keyfactor_password = var.keyfactor_password_11_5_0_AD
-# }
-
module "keyfactor_github_test_environment_11_5_0_kc" {
source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index ee81da5..a801fc3 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -15,6 +15,8 @@
package auth_providers
import (
+ "crypto/tls"
+ "crypto/x509"
"encoding/json"
"fmt"
"io"
@@ -24,14 +26,15 @@ import (
)
const (
- DefaultCommandPort = "443"
- DefaultCommandAPIPath = "KeyfactorAPI"
- DefaultAPIVersion = "1"
- DefaultAPIClientName = "APIClient"
- DefaultProductVersion = "10.5.0.0"
- EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
- EnvKeyfactorPort = "KEYFACTOR_PORT"
- EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+ DefaultCommandPort = "443"
+ DefaultCommandAPIPath = "KeyfactorAPI"
+ DefaultAPIVersion = "1"
+ DefaultAPIClientName = "APIClient"
+ DefaultProductVersion = "10.5.0.0"
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+ EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
)
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
@@ -54,8 +57,11 @@ type CommandAuthConfig struct {
// CommandAPIVersion is the version of the Keyfactor Command API, default is "1"
CommandVersion string `json:"command_version"`
- // CommandCACert is the CA certificate to be used for authentication to Keyfactor Command API for use with not widely trusted certificates
- CommandCACert string `json:"command_ca_cert;omitempty"`
+ // CommandCACert is the CA certificate to be used for authentication to Keyfactor Command API for use with not widely trusted certificates. This can be a filepath or a string of the certificate in PEM format.
+ CommandCACert string `json:"command_ca_cert"`
+
+ // SkipVerify is a flag to skip verification of the server's certificate chain and host name. Default is false.
+ SkipVerify bool `json:"skip_verify"`
// HttpClient is the http client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
@@ -87,6 +93,18 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.setClient()
+ if c.SkipVerify {
+ c.HttpClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+ return nil
+ }
+
+ caErr := c.updateCACerts()
+ if caErr != nil {
+ return caErr
+ }
+
return nil
}
@@ -97,6 +115,50 @@ func (c *CommandAuthConfig) setClient() {
}
}
+// updateCACerts updates the CA certs for the http client.
+func (c *CommandAuthConfig) updateCACerts() error {
+ // check if CommandCACert is set
+ if c.CommandCACert == "" {
+ return nil
+ }
+
+ c.setClient()
+ // Load the system certs
+ rootCAs, pErr := x509.SystemCertPool()
+ if pErr != nil {
+ return pErr
+ }
+ if rootCAs == nil {
+ rootCAs = x509.NewCertPool()
+ }
+
+ // check if CommandCACert is a file
+ if _, err := os.Stat(c.CommandCACert); err == nil {
+ cert, ioErr := os.ReadFile(c.CommandCACert)
+ if ioErr != nil {
+ return ioErr
+ }
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
+ return fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ } else {
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM([]byte(c.CommandCACert)); !ok {
+ return fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ }
+
+ // Trust the augmented cert pool in our client
+ c.HttpClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: rootCAs,
+ },
+ }
+
+ return nil
+}
+
// Authenticate performs the authentication test to Keyfactor Command API and sets Command product version.
func (c *CommandAuthConfig) Authenticate() error {
// call /Status/Endpoints API to validate credentials
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index 55fd429..e3f374f 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -15,6 +15,10 @@
package keycloak
import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "net/http"
"os"
"keyfactor_auth_client/auth_providers"
@@ -37,6 +41,9 @@ type CommandAuthConfigKeyCloak struct {
// AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
AuthType string `json:"auth_type"`
+
+ // Auth CA Cert is the CA certificate to be used for authentication to Keycloak for use with not widely trusted certificates. This can be a filepath or a string of the certificate in PEM format.
+ AuthCACert string `json:"auth_ca_cert"`
}
// ValidateAuthConfig validates the authentication configuration for Keycloak.
@@ -60,5 +67,60 @@ func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
c.AuthPort = DefaultKeyfactorAuthPort
}
}
+
+ caErr := c.updateCACerts()
+ if caErr != nil {
+ return caErr
+ }
+ return nil
+}
+
+func (c *CommandAuthConfigKeyCloak) updateCACerts() error {
+ // check if CommandCACert is set
+ if c.AuthCACert == "" {
+ return nil
+ }
+
+ // Load the system certs
+ rootCAs, pErr := x509.SystemCertPool()
+ if pErr != nil {
+ return pErr
+ }
+ if rootCAs == nil {
+ rootCAs = x509.NewCertPool()
+ }
+
+ // check if CommandCACert is a file
+ if _, err := os.Stat(c.AuthCACert); err == nil {
+ cert, ioErr := os.ReadFile(c.AuthCACert)
+ if ioErr != nil {
+ return ioErr
+ }
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
+ return fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ } else {
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM([]byte(c.AuthCACert)); !ok {
+ return fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ }
+
+ // check if client already has a tls config
+ if c.HttpClient.Transport == nil {
+ // Trust the augmented cert pool in our client
+ c.HttpClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: rootCAs,
+ },
+ }
+ } else {
+ // Trust the augmented cert pool in our client
+ c.HttpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
+ RootCAs: rootCAs,
+ }
+ }
+
return nil
}
From 77ac0bd3263c77b158685834119af3afed217a89 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 13 May 2024 09:42:13 -0700
Subject: [PATCH 16/57] fix(core): Check for `KEYFACTOR_SKIP_VERIFY` in
environment
---
.github/config/environments.tf | 13 +++++++------
.github/workflows/go_tests.yml | 1 +
auth_providers/auth_core.go | 5 +++++
3 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
index 75ffdfb..7719571 100644
--- a/.github/config/environments.tf
+++ b/.github/config/environments.tf
@@ -11,11 +11,12 @@ module "keyfactor_github_test_environment_ad_10_5_0" {
module "keyfactor_github_test_environment_11_5_0_kc" {
source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
- gh_environment_name = "KFC_11_5_0_KC"
- gh_repo_name = data.github_repository.repo.name
- keyfactor_hostname = var.keyfactor_hostname_11_5_0_KC
- keyfactor_client_id = var.keyfactor_client_id_11_5_0
- keyfactor_client_secret = var.keyfactor_client_secret_11_5_0
- keyfactor_auth_hostname = var.keyfactor_auth_hostname_11_5_0_KC
+ gh_environment_name = "KFC_11_5_0_KC"
+ gh_repo_name = data.github_repository.repo.name
+ keyfactor_hostname = var.keyfactor_hostname_11_5_0_KC
+ keyfactor_client_id = var.keyfactor_client_id_11_5_0
+ keyfactor_client_secret = var.keyfactor_client_secret_11_5_0
+ keyfactor_auth_hostname = var.keyfactor_auth_hostname_11_5_0_KC
+ keyfactor_tls_skip_verify = true
}
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index a3d10d7..cc67909 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -28,5 +28,6 @@ jobs:
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
KEYFACTOR_HOSTNAME: ${{ vars.KEYFACTOR_HOSTNAME }}
KEYFACTOR_AUTH_HOSTNAME: ${{ vars.KEYFACTOR_AUTH_HOSTNAME }}
+ KEYFACTOR_SKIP_VERIFY: ${{ vars.KEYFACTOR_SKIP_VERIFY }}
TEST_KEYFACTOR_AD_AUTH: ${{ vars.TEST_KEYFACTOR_AD_AUTH }}
TEST_KEYFACTOR_KC_AUTH: ${{ vars.TEST_KEYFACTOR_KC_AUTH }}
\ No newline at end of file
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index a801fc3..d7a3290 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -93,6 +93,11 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.setClient()
+ // check for skip verify in environment
+ if skipVerify, ok := os.LookupEnv(EnvKeyfactorSkipVerify); ok {
+ c.SkipVerify = skipVerify == "true" || skipVerify == "1"
+ }
+
if c.SkipVerify {
c.HttpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
From 40a8e219052bb9d09c891832ff9adb9af7dde7c9 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 13 May 2024 09:52:01 -0700
Subject: [PATCH 17/57] chore(docs): Update docs with CA cert env variables
---
README.md | 13 ++++++++-----
auth_providers/auth_core.go | 1 +
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 8eb30c8..32c4445 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,13 @@ Client library for authenticating to Keyfactor Command
### Global
-| Name | Description | Default |
-|--------------------|------------------------------------------------------|---------------|
-| KEYFACTOR_HOSTNAME | Keyfactor Command hostname without protocol and port | |
-| KEYFACTOR_PORT | Keyfactor Command port | `443` |
-| KEYFACTOR_API_PATH | Keyfactor Command API Path | /KeyfactorAPI |
+| Name | Description | Default |
+|-----------------------|--------------------------------------------------------------|----------------|
+| KEYFACTOR_HOSTNAME | Keyfactor Command hostname without protocol and port | |
+| KEYFACTOR_PORT | Keyfactor Command port | `443` |
+| KEYFACTOR_API_PATH | Keyfactor Command API Path | `KeyfactorAPI` |
+| KEYFACTOR_SKIP_VERIFY | Skip TLS verification when connecting to Keyfactor Command | `false` |
+| KEYFACTOR_CA_CERT | Either a file path or PEM encoded string to a CA certificate | |
### Active Directory
@@ -39,6 +41,7 @@ Client library for authenticating to Keyfactor Command
| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
| KEYFACTOR_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 | |
#### Client Credentials
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index d7a3290..38d570e 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -35,6 +35,7 @@ const (
EnvKeyfactorPort = "KEYFACTOR_PORT"
EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
+ EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
)
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
From 85f2ef794c59ffe374f9216c18159c9a9dc7a768 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 13 May 2024 09:58:42 -0700
Subject: [PATCH 18/57] feat(kc): Add support for custom auth CA cert.
---
auth_providers/auth_core.go | 9 ++++++++-
auth_providers/keycloak/keycloak_auth_client_base.go | 8 +++++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 38d570e..cf22407 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -125,10 +125,17 @@ func (c *CommandAuthConfig) setClient() {
func (c *CommandAuthConfig) updateCACerts() error {
// check if CommandCACert is set
if c.CommandCACert == "" {
- return nil
+ // check if CommandCACert is set in environment
+ if caCert, ok := os.LookupEnv(EnvKeyfactorCACert); ok {
+ c.CommandCACert = caCert
+ } else {
+ return nil
+ }
}
+ // ensure client is set
c.setClient()
+
// Load the system certs
rootCAs, pErr := x509.SystemCertPool()
if pErr != nil {
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index e3f374f..dc545bf 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -27,6 +27,7 @@ import (
const (
EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
+ EnvAuthCACert = "KEYFACTOR_AUTH_CA_CERT"
)
type CommandAuthConfigKeyCloak struct {
@@ -78,7 +79,12 @@ func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
func (c *CommandAuthConfigKeyCloak) updateCACerts() error {
// check if CommandCACert is set
if c.AuthCACert == "" {
- return nil
+ // check environment for auth CA cert
+ if authCACert, ok := os.LookupEnv(EnvAuthCACert); ok {
+ c.AuthCACert = authCACert
+ } else {
+ return nil
+ }
}
// Load the system certs
From 9f47aa57bdcb64be4f16306d8dc5f4e97f6ad455 Mon Sep 17 00:00:00 2001
From: sbailey <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:11:51 -0700
Subject: [PATCH 19/57] feat(core): Add base oauth client that uses
`golang.org/x/oauth2`
---
auth_providers/auth_core.go | 15 +-
auth_providers/auth_oauth.go | 186 ++++++++++++++++++
.../keycloak_auth_client_credentials.go | 37 +++-
go.mod | 2 +
4 files changed, 226 insertions(+), 14 deletions(-)
create mode 100644 auth_providers/auth_oauth.go
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index cf22407..a960a69 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -91,8 +91,7 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.CommandAPIPath = DefaultCommandAPIPath
}
}
-
- c.setClient()
+ c.SetClient(nil)
// check for skip verify in environment
if skipVerify, ok := os.LookupEnv(EnvKeyfactorSkipVerify); ok {
@@ -114,11 +113,15 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
return nil
}
-// setClient sets the http client for authentication to Keyfactor Command API.
-func (c *CommandAuthConfig) setClient() {
+// SetClient sets the http client for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) SetClient(client *http.Client) *http.Client {
+ if client != nil {
+ c.HttpClient = client
+ }
if c.HttpClient == nil {
c.HttpClient = &http.Client{}
}
+ return c.HttpClient
}
// updateCACerts updates the CA certs for the http client.
@@ -134,7 +137,7 @@ func (c *CommandAuthConfig) updateCACerts() error {
}
// ensure client is set
- c.setClient()
+ c.SetClient(nil)
// Load the system certs
rootCAs, pErr := x509.SystemCertPool()
@@ -175,7 +178,7 @@ func (c *CommandAuthConfig) updateCACerts() error {
// Authenticate performs the authentication test to Keyfactor Command API and sets Command product version.
func (c *CommandAuthConfig) Authenticate() error {
// call /Status/Endpoints API to validate credentials
- c.setClient()
+ c.SetClient(nil)
//create headers for request
headers := map[string]string{
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
new file mode 100644
index 0000000..0af145b
--- /dev/null
+++ b/auth_providers/auth_oauth.go
@@ -0,0 +1,186 @@
+package auth_providers
+
+import (
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/clientcredentials"
+)
+
+type Authenticator interface {
+ GetHttpClient() (*http.Client, error)
+}
+
+// OAuth Authenticator
+
+var _ Authenticator = &OAuthAuthenticator{}
+
+// OAuthAuthenticator is an Authenticator that uses OAuth2 for authentication.
+type OAuthAuthenticator struct {
+ client *http.Client
+}
+
+type OAuthAuthenticatorBuilder struct {
+ clientId string
+ clientSecret string
+ tokenUrl string
+ audience string
+ scopes []string
+ caCertificatePath string
+ caCertificates []*x509.Certificate
+}
+
+func NewOAuthAuthenticatorBuilder() *OAuthAuthenticatorBuilder {
+ return &OAuthAuthenticatorBuilder{}
+}
+
+func (b *OAuthAuthenticatorBuilder) WithClientId(clientId string) *OAuthAuthenticatorBuilder {
+ b.clientId = clientId
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithClientSecret(clientSecret string) *OAuthAuthenticatorBuilder {
+ b.clientSecret = clientSecret
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithTokenUrl(tokenUrl string) *OAuthAuthenticatorBuilder {
+ b.tokenUrl = tokenUrl
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithScopes(scopes []string) *OAuthAuthenticatorBuilder {
+ b.scopes = scopes
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithAudience(audience string) *OAuthAuthenticatorBuilder {
+ b.audience = audience
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithCaCertificatePath(caCertificatePath string) *OAuthAuthenticatorBuilder {
+ b.caCertificatePath = caCertificatePath
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) WithCaCertificates(caCertificates []*x509.Certificate) *OAuthAuthenticatorBuilder {
+ b.caCertificates = caCertificates
+ return b
+}
+
+func (b *OAuthAuthenticatorBuilder) Build() (Authenticator, error) {
+ config := &clientcredentials.Config{
+ ClientID: b.clientId,
+ ClientSecret: b.clientSecret,
+ TokenURL: b.tokenUrl,
+ Scopes: b.scopes,
+ }
+
+ if b.audience != "" {
+ config.EndpointParams = map[string][]string{
+ "audience": {
+ b.audience,
+ },
+ }
+ }
+
+ tokenSource := config.TokenSource(context.Background())
+ oauthTransport := &oauth2.Transport{
+ Base: http.DefaultTransport,
+ Source: tokenSource,
+ }
+
+ if b.caCertificates == nil {
+ var err error
+ b.caCertificates, err = findCaCertificate(b.caCertificatePath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to find CA certificates: %w", err)
+ }
+ }
+
+ if len(b.caCertificates) > 0 {
+ tlsConfig := &tls.Config{
+ Renegotiation: tls.RenegotiateOnceAsClient,
+ }
+
+ tlsConfig.RootCAs = x509.NewCertPool()
+ for _, caCert := range b.caCertificates {
+ tlsConfig.RootCAs.AddCert(caCert)
+ }
+
+ customTransport := http.DefaultTransport.(*http.Transport).Clone()
+ customTransport.TLSClientConfig = tlsConfig
+ customTransport.TLSHandshakeTimeout = 10 * time.Second
+
+ // Wrap the custom transport with the oauth2.Transport
+ oauthTransport.Base = customTransport
+ }
+
+ client := &http.Client{
+ Transport: oauthTransport,
+ }
+
+ return &OAuthAuthenticator{client: client}, nil
+}
+
+func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
+ return a.client, nil
+}
+
+func findCaCertificate(caCertificatePath string) ([]*x509.Certificate, error) {
+ if caCertificatePath == "" {
+ return nil, nil
+ }
+
+ buf, err := os.ReadFile(caCertificatePath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read CA certificate file at path %s: %w", caCertificatePath, err)
+ }
+ // Decode the PEM encoded certificates into a slice of PEM blocks
+ chainBlocks, _, err := decodePEMBytes(buf)
+ if err != nil {
+ return nil, err
+ }
+ if len(chainBlocks) <= 0 {
+ return nil, fmt.Errorf("didn't find certificate in file at path %s", caCertificatePath)
+ }
+
+ var caChain []*x509.Certificate
+ for _, block := range chainBlocks {
+ // Parse the PEM block into an x509 certificate
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
+ }
+
+ caChain = append(caChain, cert)
+ }
+
+ return caChain, nil
+}
+
+func decodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
+ var privKey []byte
+ var certificates []*pem.Block
+ var block *pem.Block
+ for {
+ block, buf = pem.Decode(buf)
+ if block == nil {
+ break
+ } else if strings.Contains(block.Type, "PRIVATE KEY") {
+ privKey = pem.EncodeToMemory(block)
+ } else {
+ certificates = append(certificates, block)
+ }
+ }
+ return certificates, privKey, nil
+}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index a93601e..54d7078 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -23,16 +23,18 @@ import (
"os"
"strings"
"time"
+
+ "keyfactor_auth_client/auth_providers"
)
const (
- DefaultKeyfactorAuthPort = "8444"
- DefaultKeyfactorRealm = "Keyfactor"
- EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
- EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
- EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
- EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
- EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
+ DefaultKeyfactorAuthPort = "8444"
+ DefaultKeyfactorAuthRealm = "Keyfactor"
+ EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
+ EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
+ EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
+ EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
+ EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
)
// CommandAuthKeyCloakClientCredentials represents the configuration needed for Keycloak authentication using client credentials.
@@ -87,6 +89,25 @@ func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
c.AuthHeader = fmt.Sprintf("Bearer %s", token)
+ // create oauth client
+ oauthy, err := auth_providers.NewOAuthAuthenticatorBuilder().
+ WithClientId(c.ClientID).
+ WithClientSecret(c.ClientSecret).
+ WithTokenUrl(c.TokenURL).
+ Build()
+
+ if err != nil {
+ return err
+ }
+
+ if oauthy != nil {
+ oClient, oerr := oauthy.GetHttpClient()
+ if oerr != nil {
+ return oerr
+ }
+ c.SetClient(oClient)
+ }
+
aErr := c.CommandAuthConfig.Authenticate()
if aErr != nil {
return aErr
@@ -128,7 +149,7 @@ func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
if realm, ok := os.LookupEnv(EnvKeyfactorAuthRealm); ok {
c.Realm = realm
} else {
- c.Realm = DefaultKeyfactorRealm
+ c.Realm = DefaultKeyfactorAuthRealm
}
}
return nil
diff --git a/go.mod b/go.mod
index 557ec42..0cec800 100644
--- a/go.mod
+++ b/go.mod
@@ -15,3 +15,5 @@
module keyfactor_auth_client
go 1.22
+
+require golang.org/x/oauth2 v0.22.0
From 0b51af07c35cd0917661cd26c06adbd98672981d Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 07:07:43 -0700
Subject: [PATCH 20/57] fix(module): Add go.sum and name module correctly.
---
go.mod | 2 +-
go.sum | 4 ++++
tag.sh | 5 +++++
3 files changed, 10 insertions(+), 1 deletion(-)
create mode 100644 go.sum
create mode 100755 tag.sh
diff --git a/go.mod b/go.mod
index 0cec800..e7809d2 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-module keyfactor_auth_client
+module keyfactor-auth-client-go
go 1.22
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..280e973
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
+golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
diff --git a/tag.sh b/tag.sh
new file mode 100755
index 0000000..936ef91
--- /dev/null
+++ b/tag.sh
@@ -0,0 +1,5 @@
+RC_VERSION=rc.1
+TAG_VERSION_1=v0.0.1-$RC_VERSION
+git tag -d $TAG_VERSION_1 || true
+git tag $TAG_VERSION_1
+git push origin $TAG_VERSION_1
\ No newline at end of file
From 8073944f0ee6bb32b67cf86f1e13a14165716fea Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 07:12:11 -0700
Subject: [PATCH 21/57] fix(module): name module correctly.
---
go.mod | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/go.mod b/go.mod
index e7809d2..c1abd39 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-module keyfactor-auth-client-go
+module github.com/Keyfactor/keyfactor-auth-client-go
go 1.22
From cd02c0786111e4f67a494a5147d6ee99b0aa7314 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 07:21:53 -0700
Subject: [PATCH 22/57] fix(module): ref name module correctly.
---
auth_providers/active_directory/ad_auth.go | 2 +-
auth_providers/keycloak/keycloak_auth_client_base.go | 2 +-
auth_providers/keycloak/keycloak_auth_client_credentials.go | 2 +-
main.go | 2 +-
tag.sh | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
index 42fe0a1..1c6a559 100644
--- a/auth_providers/active_directory/ad_auth.go
+++ b/auth_providers/active_directory/ad_auth.go
@@ -20,7 +20,7 @@ import (
"os"
"strings"
- "keyfactor_auth_client/auth_providers"
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
const (
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index dc545bf..66aa6a5 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -21,7 +21,7 @@ import (
"net/http"
"os"
- "keyfactor_auth_client/auth_providers"
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
const (
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index 54d7078..6462871 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -24,7 +24,7 @@ import (
"strings"
"time"
- "keyfactor_auth_client/auth_providers"
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
const (
diff --git a/main.go b/main.go
index 865aef8..f1615e2 100644
--- a/main.go
+++ b/main.go
@@ -17,7 +17,7 @@ package main
import (
"fmt"
- "keyfactor_auth_client/pkg"
+ "github.com/Keyfactor/keyfactor-auth-client-go/pkg" // Correct
)
func main() {
diff --git a/tag.sh b/tag.sh
index 936ef91..4f82a33 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.1
+RC_VERSION=rc.2
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 0573ca65f37f783ebcb7692da81f6501d3ddcd6f Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 08:10:28 -0700
Subject: [PATCH 23/57] feat(core): Add `KEYFACTOR_AUTH_PROVIDER` environmental
variable.
---
auth_providers/auth_core.go | 21 +++++++++++----------
tag.sh | 2 +-
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index a960a69..c7c9f04 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -26,16 +26,17 @@ import (
)
const (
- DefaultCommandPort = "443"
- DefaultCommandAPIPath = "KeyfactorAPI"
- DefaultAPIVersion = "1"
- DefaultAPIClientName = "APIClient"
- DefaultProductVersion = "10.5.0.0"
- EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
- EnvKeyfactorPort = "KEYFACTOR_PORT"
- EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
- EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
- EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
+ DefaultCommandPort = "443"
+ DefaultCommandAPIPath = "KeyfactorAPI"
+ DefaultAPIVersion = "1"
+ DefaultAPIClientName = "APIClient"
+ DefaultProductVersion = "10.5.0.0"
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+ EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
+ EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
+ EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
)
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
diff --git a/tag.sh b/tag.sh
index 4f82a33..f8d6436 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.2
+RC_VERSION=rc.4
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From e1916133d295bb8a25a7d1e394d40e74b0c47a85 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 10:15:51 -0700
Subject: [PATCH 24/57] feat(core): Use custom HTTP transport for basic auth
client.
---
auth_providers/auth_basic.go | 110 +++++++++++
auth_providers/auth_core.go | 91 ++++++++-
auth_providers/auth_oauth.go | 183 ++++++++++--------
.../keycloak/keycloak_auth_client_base.go | 2 +-
.../keycloak_auth_client_credentials.go | 47 ++---
.../keycloak_auth_client_credentials_test.go | 9 +-
tag.sh | 2 +-
7 files changed, 327 insertions(+), 117 deletions(-)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 0b2f5d3..8a09fa3 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -14,6 +14,53 @@
package auth_providers
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+)
+
+var _ Authenticator = &BasicAuthAuthenticator{}
+
+type BasicAuthAuthenticator struct {
+ client *http.Client
+}
+
+func BasicAuthTransport(username, password string) *http.Client {
+ // Encode the username and password in Base64
+ auth := username + ":" + password
+ encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
+
+ // Create a custom RoundTripper
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ // You can customize other transport settings here
+ }
+
+ return &http.Client{
+ Transport: roundTripperFunc(
+ func(req *http.Request) (*http.Response, error) {
+ // Add the Authorization header to the request
+ req.Header.Set("Authorization", "Basic "+encodedAuth)
+
+ // Forward the request to the actual transport
+ return transport.RoundTrip(req)
+ },
+ ),
+ }
+}
+
+// roundTripperFunc is a helper type to create a custom RoundTripper
+type roundTripperFunc func(req *http.Request) (*http.Response, error)
+
+func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+ return f(req)
+}
+
+func (b *BasicAuthAuthenticator) GetHttpClient() (*http.Client, error) {
+ return b.client, nil
+}
+
// CommandAuthConfigBasic represents the base configuration needed for authentication to Keyfactor Command API.
type CommandAuthConfigBasic struct {
// CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
@@ -25,3 +72,66 @@ type CommandAuthConfigBasic struct {
// Password is the password to be used for authentication to Keyfactor Command API
Password string `json:"password"`
}
+
+func NewBasicAuthAuthenticatorBuilder() *CommandAuthConfigBasic {
+ return &CommandAuthConfigBasic{}
+}
+
+func (a *CommandAuthConfigBasic) WithUsername(username string) *CommandAuthConfigBasic {
+ a.Username = username
+ return a
+}
+
+func (a *CommandAuthConfigBasic) WithPassword(password string) *CommandAuthConfigBasic {
+ a.Password = password
+ return a
+}
+
+func (a *CommandAuthConfigBasic) Build() (Authenticator, error) {
+
+ client := BasicAuthTransport(a.Username, a.Password)
+ a.HttpClient = client
+
+ return &BasicAuthAuthenticator{client: client}, nil
+}
+
+func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
+ if a.Username == "" {
+ return fmt.Errorf("username is required")
+ }
+ if a.Password == "" {
+ return fmt.Errorf("password is required")
+ }
+
+ return a.CommandAuthConfig.ValidateAuthConfig()
+}
+
+func (a *CommandAuthConfigBasic) Authenticate() error {
+ cErr := a.ValidateAuthConfig()
+ if cErr != nil {
+ return cErr
+ }
+ //basicAuth := fmt.Sprintf("%s:%s", c.Username, c.Password)
+ //basicAuth = base64.StdEncoding.EncodeToString([]byte(basicAuth))
+ //c.AuthHeader = fmt.Sprintf("Basic %s", basicAuth)
+
+ // create oauth client
+ authy, err := NewBasicAuthAuthenticatorBuilder().
+ WithUsername(a.Username).
+ WithPassword(a.Password).
+ Build()
+
+ if err != nil {
+ return err
+ }
+
+ if authy != nil {
+ bClient, berr := authy.GetHttpClient()
+ if berr != nil {
+ return berr
+ }
+ a.SetClient(bClient)
+ }
+
+ return a.CommandAuthConfig.Authenticate()
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index c7c9f04..4394a5d 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -18,10 +18,12 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
+ "encoding/pem"
"fmt"
"io"
"net/http"
"os"
+ "strings"
"time"
)
@@ -39,6 +41,11 @@ const (
EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
)
+// Authenticator is an interface for authentication to Keyfactor Command API.
+type Authenticator interface {
+ GetHttpClient() (*http.Client, error)
+}
+
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
type CommandAuthConfig struct {
// ConfigType is the type of configuration
@@ -69,6 +76,36 @@ type CommandAuthConfig struct {
HttpClient *http.Client
}
+func (c *CommandAuthConfig) WithCommandHostName(hostName string) *CommandAuthConfig {
+ c.CommandHostName = hostName
+ return c
+}
+
+func (c *CommandAuthConfig) WithCommandPort(port string) *CommandAuthConfig {
+ c.CommandPort = port
+ return c
+}
+
+func (c *CommandAuthConfig) WithCommandAPIPath(apiPath string) *CommandAuthConfig {
+ c.CommandAPIPath = apiPath
+ return c
+}
+
+func (c *CommandAuthConfig) WithCommandCACert(caCert string) *CommandAuthConfig {
+ c.CommandCACert = caCert
+ return c
+}
+
+func (c *CommandAuthConfig) WithSkipVerify(skipVerify bool) *CommandAuthConfig {
+ c.SkipVerify = skipVerify
+ return c
+}
+
+func (c *CommandAuthConfig) WithHttpClient(client *http.Client) *CommandAuthConfig {
+ c.HttpClient = client
+ return c
+}
+
// ValidateAuthConfig validates the authentication configuration for Keyfactor Command API.
func (c *CommandAuthConfig) ValidateAuthConfig() error {
if c.CommandHostName == "" {
@@ -185,11 +222,14 @@ func (c *CommandAuthConfig) Authenticate() error {
headers := map[string]string{
"Content-Type": "application/json",
"Accept": "application/json",
- "Authorization": c.AuthHeader,
"x-keyfactor-api-version": DefaultAPIVersion,
"x-keyfactor-requested-with": DefaultAPIClientName,
}
+ if c.AuthHeader != "" {
+ headers["Authorization"] = c.AuthHeader
+ }
+
endPoint := fmt.Sprintf(
"https://%s/%s/Status/Endpoints",
c.CommandHostName,
@@ -253,3 +293,52 @@ func (c *CommandAuthConfig) Authenticate() error {
return nil
}
+
+func FindCACertificate(caCertificatePath string) ([]*x509.Certificate, error) {
+ if caCertificatePath == "" {
+ return nil, nil
+ }
+
+ buf, err := os.ReadFile(caCertificatePath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read CA certificate file at path %s: %w", caCertificatePath, err)
+ }
+ // Decode the PEM encoded certificates into a slice of PEM blocks
+ chainBlocks, _, err := DecodePEMBytes(buf)
+ if err != nil {
+ return nil, err
+ }
+ if len(chainBlocks) <= 0 {
+ return nil, fmt.Errorf("didn't find certificate in file at path %s", caCertificatePath)
+ }
+
+ var caChain []*x509.Certificate
+ for _, block := range chainBlocks {
+ // Parse the PEM block into an x509 certificate
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
+ }
+
+ caChain = append(caChain, cert)
+ }
+
+ return caChain, nil
+}
+
+func DecodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
+ var privKey []byte
+ var certificates []*pem.Block
+ var block *pem.Block
+ for {
+ block, buf = pem.Decode(buf)
+ if block == nil {
+ break
+ } else if strings.Contains(block.Type, "PRIVATE KEY") {
+ privKey = pem.EncodeToMemory(block)
+ } else {
+ certificates = append(certificates, block)
+ }
+ }
+ return certificates, privKey, nil
+}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 0af145b..051ad22 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -4,23 +4,35 @@ import (
"context"
"crypto/tls"
"crypto/x509"
- "encoding/pem"
"fmt"
"net/http"
- "os"
- "strings"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
-type Authenticator interface {
- GetHttpClient() (*http.Client, error)
-}
+const (
+ // DefaultKeyfactorAuthPort is the default port for Keyfactor authentication
+ DefaultKeyfactorAuthPort = "8444"
-// OAuth Authenticator
+ // DefaultTokenPrefix is the default token prefix for Keyfactor authentication headers
+ DefaultTokenPrefix = "Bearer"
+
+ // EnvKeyfactorClientID is the environment variable used to set the client ID for oauth client credentials authentication
+ EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
+
+ // EnvKeyfactorClientSecret is the environment variable used to set the client secret for oauth client credentials authentication
+ EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
+ // EnvKeyfactorAuthTokenURL EnvCommandTokenURL is the environment variable used to set the token URL for oauth client credentials authentication
+ EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
+
+ // EnvKeyfactorAccessToken is the environment variable used to set the access token for oauth client credentials authentication
+ EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
+)
+
+// OAuth Authenticator
var _ Authenticator = &OAuthAuthenticator{}
// OAuthAuthenticator is an Authenticator that uses OAuth2 for authentication.
@@ -28,67 +40,72 @@ type OAuthAuthenticator struct {
client *http.Client
}
-type OAuthAuthenticatorBuilder struct {
- clientId string
- clientSecret string
- tokenUrl string
- audience string
- scopes []string
- caCertificatePath string
- caCertificates []*x509.Certificate
+func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
+ return a.client, nil
+}
+
+type CommandConfigOauth struct {
+ CommandAuthConfig
+ ClientID string `json:"client_id,omitempty"`
+ ClientSecret string `json:"client_secret,omitempty"`
+ TokenURL string `json:"token_url,omitempty"`
+ Audience string `json:"audience,omitempty"`
+ Scopes []string `json:"scopes,omitempty"`
+ CACertificatePath string `json:"idp_ca_cert_path,omitempty"`
+ CACertificates []*x509.Certificate `json:"-"`
}
-func NewOAuthAuthenticatorBuilder() *OAuthAuthenticatorBuilder {
- return &OAuthAuthenticatorBuilder{}
+func NewOAuthAuthenticatorBuilder() *CommandConfigOauth {
+ return &CommandConfigOauth{}
}
-func (b *OAuthAuthenticatorBuilder) WithClientId(clientId string) *OAuthAuthenticatorBuilder {
- b.clientId = clientId
+func (b *CommandConfigOauth) WithClientId(clientId string) *CommandConfigOauth {
+ b.ClientID = clientId
return b
}
-func (b *OAuthAuthenticatorBuilder) WithClientSecret(clientSecret string) *OAuthAuthenticatorBuilder {
- b.clientSecret = clientSecret
+func (b *CommandConfigOauth) WithClientSecret(clientSecret string) *CommandConfigOauth {
+ b.ClientSecret = clientSecret
return b
}
-func (b *OAuthAuthenticatorBuilder) WithTokenUrl(tokenUrl string) *OAuthAuthenticatorBuilder {
- b.tokenUrl = tokenUrl
+func (b *CommandConfigOauth) WithTokenUrl(tokenUrl string) *CommandConfigOauth {
+ b.TokenURL = tokenUrl
return b
}
-func (b *OAuthAuthenticatorBuilder) WithScopes(scopes []string) *OAuthAuthenticatorBuilder {
- b.scopes = scopes
+func (b *CommandConfigOauth) WithScopes(scopes []string) *CommandConfigOauth {
+ b.Scopes = scopes
return b
}
-func (b *OAuthAuthenticatorBuilder) WithAudience(audience string) *OAuthAuthenticatorBuilder {
- b.audience = audience
+func (b *CommandConfigOauth) WithAudience(audience string) *CommandConfigOauth {
+ b.Audience = audience
return b
}
-func (b *OAuthAuthenticatorBuilder) WithCaCertificatePath(caCertificatePath string) *OAuthAuthenticatorBuilder {
- b.caCertificatePath = caCertificatePath
+func (b *CommandConfigOauth) WithCaCertificatePath(caCertificatePath string) *CommandConfigOauth {
+ b.CACertificatePath = caCertificatePath
return b
}
-func (b *OAuthAuthenticatorBuilder) WithCaCertificates(caCertificates []*x509.Certificate) *OAuthAuthenticatorBuilder {
- b.caCertificates = caCertificates
+func (b *CommandConfigOauth) WithCaCertificates(caCertificates []*x509.Certificate) *CommandConfigOauth {
+ b.CACertificates = caCertificates
return b
}
-func (b *OAuthAuthenticatorBuilder) Build() (Authenticator, error) {
+func (b *CommandConfigOauth) Build() (Authenticator, error) {
config := &clientcredentials.Config{
- ClientID: b.clientId,
- ClientSecret: b.clientSecret,
- TokenURL: b.tokenUrl,
- Scopes: b.scopes,
+ ClientID: b.ClientID,
+ ClientSecret: b.ClientSecret,
+ TokenURL: b.TokenURL,
+ Scopes: b.Scopes,
}
- if b.audience != "" {
+ if b.Audience != "" {
config.EndpointParams = map[string][]string{
- "audience": {
- b.audience,
+ "Audience": {
+ b.Audience,
},
}
}
@@ -99,21 +116,21 @@ func (b *OAuthAuthenticatorBuilder) Build() (Authenticator, error) {
Source: tokenSource,
}
- if b.caCertificates == nil {
+ if b.CACertificates == nil {
var err error
- b.caCertificates, err = findCaCertificate(b.caCertificatePath)
+ b.CACertificates, err = FindCACertificate(b.CACertificatePath)
if err != nil {
return nil, fmt.Errorf("failed to find CA certificates: %w", err)
}
}
- if len(b.caCertificates) > 0 {
+ if len(b.CACertificates) > 0 {
tlsConfig := &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
}
tlsConfig.RootCAs = x509.NewCertPool()
- for _, caCert := range b.caCertificates {
+ for _, caCert := range b.CACertificates {
tlsConfig.RootCAs.AddCert(caCert)
}
@@ -132,55 +149,57 @@ func (b *OAuthAuthenticatorBuilder) Build() (Authenticator, error) {
return &OAuthAuthenticator{client: client}, nil
}
-func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
- return a.client, nil
-}
-
-func findCaCertificate(caCertificatePath string) ([]*x509.Certificate, error) {
- if caCertificatePath == "" {
- return nil, nil
+func (b *CommandConfigOauth) ValidateAuthConfig() error {
+ if b.ClientID == "" {
+ return fmt.Errorf("client ID is required")
}
- buf, err := os.ReadFile(caCertificatePath)
- if err != nil {
- return nil, fmt.Errorf("failed to read CA certificate file at path %s: %w", caCertificatePath, err)
+ if b.ClientSecret == "" {
+ return fmt.Errorf("client secret is required")
}
- // Decode the PEM encoded certificates into a slice of PEM blocks
- chainBlocks, _, err := decodePEMBytes(buf)
- if err != nil {
- return nil, err
+
+ if b.TokenURL == "" {
+ return fmt.Errorf("token URL is required")
}
- if len(chainBlocks) <= 0 {
- return nil, fmt.Errorf("didn't find certificate in file at path %s", caCertificatePath)
+
+ if len(b.Scopes) == 0 {
+ return fmt.Errorf("at least one scope is required")
}
- var caChain []*x509.Certificate
- for _, block := range chainBlocks {
- // Parse the PEM block into an x509 certificate
- cert, err := x509.ParseCertificate(block.Bytes)
- if err != nil {
- return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
- }
+ return b.CommandAuthConfig.ValidateAuthConfig()
+}
+
+func (b *CommandConfigOauth) Authenticate() error {
- caChain = append(caChain, cert)
+ // validate auth config
+ vErr := b.ValidateAuthConfig()
+ if vErr != nil {
+ return vErr
}
- return caChain, nil
-}
+ // create oauth client
+ oauthy, err := NewOAuthAuthenticatorBuilder().
+ WithClientId(b.ClientID).
+ WithClientSecret(b.ClientSecret).
+ WithTokenUrl(b.TokenURL).
+ Build()
+
+ if err != nil {
+ return err
+ }
-func decodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
- var privKey []byte
- var certificates []*pem.Block
- var block *pem.Block
- for {
- block, buf = pem.Decode(buf)
- if block == nil {
- break
- } else if strings.Contains(block.Type, "PRIVATE KEY") {
- privKey = pem.EncodeToMemory(block)
- } else {
- certificates = append(certificates, block)
+ if oauthy != nil {
+ oClient, oerr := oauthy.GetHttpClient()
+ if oerr != nil {
+ return oerr
}
+ b.SetClient(oClient)
}
- return certificates, privKey, nil
+
+ aErr := b.CommandAuthConfig.Authenticate()
+ if aErr != nil {
+ return aErr
+ }
+
+ return nil
}
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index 66aa6a5..9d5cd99 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -65,7 +65,7 @@ func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
if port, ok := os.LookupEnv(EnvKeyfactorAuthPort); ok {
c.AuthPort = port
} else {
- c.AuthPort = DefaultKeyfactorAuthPort
+ c.AuthPort = auth_providers.DefaultKeyfactorAuthPort
}
}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
index 6462871..b5d42ed 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials.go
@@ -28,13 +28,8 @@ import (
)
const (
- DefaultKeyfactorAuthPort = "8444"
DefaultKeyfactorAuthRealm = "Keyfactor"
- EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
- EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
- EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
- EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
)
// CommandAuthKeyCloakClientCredentials represents the configuration needed for Keycloak authentication using client credentials.
@@ -68,26 +63,21 @@ type CommandAuthKeyCloakClientCredentials struct {
// Authenticate performs the authentication process for Keycloak using client credentials.
// It validates the authentication configuration, gets the token, and calls the base authentication method.
func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
- cErr := c.CommandAuthConfig.ValidateAuthConfig() // Validate base config
- if cErr != nil {
- return cErr
- }
-
c.AuthType = "client_credentials"
- cErr = c.ValidateAuthConfig()
+ cErr := c.ValidateAuthConfig()
if cErr != nil {
return cErr
}
- token, tErr := c.GetToken()
- if tErr != nil {
- return tErr
- }
- if token == "" {
- return fmt.Errorf("failed to get Bearer token using client credentials")
- }
-
- c.AuthHeader = fmt.Sprintf("Bearer %s", token)
+ //token, tErr := c.GetToken()
+ //if tErr != nil {
+ // return tErr
+ //}
+ //if token == "" {
+ // return fmt.Errorf("failed to get Bearer token using client credentials")
+ //}
+ //
+ //c.AuthHeader = fmt.Sprintf("Bearer %s", token)
// create oauth client
oauthy, err := auth_providers.NewOAuthAuthenticatorBuilder().
@@ -120,10 +110,10 @@ func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
// It retrieves the client ID from environment variables if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
if c.ClientID == "" {
- if clientID, ok := os.LookupEnv(EnvKeyfactorClientID); ok {
+ if clientID, ok := os.LookupEnv(auth_providers.EnvKeyfactorClientID); ok {
c.ClientID = clientID
} else {
- return fmt.Errorf("client_id or environment variable %s is required", EnvKeyfactorClientID)
+ return fmt.Errorf("client_id or environment variable %s is required", auth_providers.EnvKeyfactorClientID)
}
}
return nil
@@ -133,10 +123,13 @@ func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
// It retrieves the client secret from environment variables if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setClientSecret() error {
if c.ClientSecret == "" {
- if clientSecret, ok := os.LookupEnv(EnvKeyfactorClientSecret); ok {
+ if clientSecret, ok := os.LookupEnv(auth_providers.EnvKeyfactorClientSecret); ok {
c.ClientSecret = clientSecret
} else {
- return fmt.Errorf("client_secret or environment variable %s is required", EnvKeyfactorClientSecret)
+ return fmt.Errorf(
+ "client_secret or environment variable %s is required",
+ auth_providers.EnvKeyfactorClientSecret,
+ )
}
}
return nil
@@ -159,7 +152,7 @@ func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
// It generates the token URL if it's not set.
func (c *CommandAuthKeyCloakClientCredentials) setTokenURL() error {
if c.TokenURL == "" {
- if tokenURL, ok := os.LookupEnv(EnvKeyfactorAuthTokenURL); ok {
+ if tokenURL, ok := os.LookupEnv(auth_providers.EnvKeyfactorAuthTokenURL); ok {
c.TokenURL = tokenURL
} else {
c.TokenURL = fmt.Sprintf(
@@ -202,7 +195,7 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
}
- return nil
+ return c.CommandAuthConfig.ValidateAuthConfig()
}
// GetToken gets the access token for Keycloak authentication.
@@ -210,7 +203,7 @@ func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
// Check if access token is set in environment variable
if c.AccessToken == "" {
- if accessToken, ok := os.LookupEnv(EnvKeyfactorAccessToken); ok {
+ if accessToken, ok := os.LookupEnv(auth_providers.EnvKeyfactorAccessToken); ok {
c.AccessToken = accessToken
// Don't try to refresh as we don't have a refresh token
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
index ab10428..64ed06f 100644
--- a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
+++ b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
@@ -15,7 +15,6 @@
package keycloak
import (
- "fmt"
"os"
"testing"
)
@@ -44,10 +43,10 @@ func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
}
// Check that the AuthHeader was set correctly
- expectedAuthHeader := fmt.Sprintf("Bearer %s", c.AccessToken)
- if c.AuthHeader != expectedAuthHeader {
- t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
- }
+ //expectedAuthHeader := fmt.Sprintf("Bearer %s", c.AccessToken)
+ //if c.AuthHeader != expectedAuthHeader {
+ // t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
+ //} else if c.HttpClient.Tra
}
func checkAuthEnvClientCreds() bool {
diff --git a/tag.sh b/tag.sh
index f8d6436..c1e0379 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.4
+RC_VERSION=rc.5
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 7bc0781f2a6b3eb0fbc3bac6e609e0f970c51088 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 10:22:52 -0700
Subject: [PATCH 25/57] fix(oauth): Add environment vars:
`KEYFACTOR_AUTH_AUDIENCE, KEYFACTOR_AUTH_SCOPES`
---
auth_providers/auth_oauth.go | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 051ad22..a4cc016 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -30,6 +30,13 @@ const (
// EnvKeyfactorAccessToken is the environment variable used to set the access token for oauth client credentials authentication
EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
+
+ // EnvKeyfactorAuthAudience is the environment variable used to set the audience for oauth client credentials
+ //authentication
+ EnvKeyfactorAuthAudience = "KEYFACTOR_AUTH_AUDIENCE"
+
+ // EnvKeyfactorAuthScopes is the environment variable used to set the scopes for oauth client credentials authentication
+ EnvKeyfactorAuthScopes = "KEYFACTOR_AUTH_SCOPES"
)
// OAuth Authenticator
From 4729d45bf7976e6a306be6f4139c1712dcee970c Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 3 Sep 2024 10:28:48 -0700
Subject: [PATCH 26/57] fix(oauth): Scope `KEYFACTOR_AUTH_HOSTNAME,
KEYFACTOR_AUTH_PORT, KEYFACTOR_AUTH_CA_CERT` to `auth_oauth.go`
---
auth_providers/auth_oauth.go | 9 +++++++++
auth_providers/keycloak/keycloak_auth_client_base.go | 6 ------
tag.sh | 2 +-
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index a4cc016..df51e35 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -37,6 +37,15 @@ const (
// EnvKeyfactorAuthScopes is the environment variable used to set the scopes for oauth client credentials authentication
EnvKeyfactorAuthScopes = "KEYFACTOR_AUTH_SCOPES"
+
+ // EnvKeyfactorAuthHostname is the environment variable used to set the hostname for oauth client credentials authentication
+ EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
+
+ // EnvKeyfactorAuthPort is the environment variable used to set the port for oauth client credentials authentication
+ EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
+
+ // EnvAuthCACert is a path to a CA certificate for the OAuth client credentials authentication
+ EnvAuthCACert = "KEYFACTOR_AUTH_CA_CERT"
)
// OAuth Authenticator
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
index 9d5cd99..48d94b8 100644
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ b/auth_providers/keycloak/keycloak_auth_client_base.go
@@ -24,12 +24,6 @@ import (
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
-const (
- EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
- EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
- EnvAuthCACert = "KEYFACTOR_AUTH_CA_CERT"
-)
-
type CommandAuthConfigKeyCloak struct {
// CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
auth_providers.CommandAuthConfig
diff --git a/tag.sh b/tag.sh
index c1e0379..1f84e80 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.5
+RC_VERSION=rc.6
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 559cefef4da9f76e28683e6778280707217e3d68 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 16 Oct 2024 16:42:55 -0700
Subject: [PATCH 27/57] feat(core): Simplify oauth and basic auth feat(core):
Add ability to load from config file on disk and/or environmental variables
feat(tests): Add tests for auth methods
---
.github/config/variables.tf | 4 +-
.../workflows/keyfactor-starter-workflow.yml | 3 +-
auth_config/auth_config_example.json | 42 +++
auth_config/auth_config_schema.json | 110 +++++++
auth_config/command_config.go | 173 +++++++++++
auth_providers/active_directory/ad_auth.go | 129 --------
.../active_directory/ad_auth_test.go | 59 ----
auth_providers/auth_basic.go | 121 +++++---
auth_providers/auth_basic_test.go | 77 +++++
auth_providers/auth_core.go | 243 +++++++++++++--
auth_providers/auth_core_test.go | 90 ++++++
auth_providers/auth_oauth.go | 140 +++++----
auth_providers/auth_oauth_test.go | 87 ++++++
.../keycloak/keycloak_auth_certificate.go | 15 -
.../keycloak/keycloak_auth_client_base.go | 126 --------
.../keycloak_auth_client_credentials.go | 286 ------------------
.../keycloak_auth_client_credentials_test.go | 59 ----
go.mod | 5 +-
go.sum | 4 +
lib/main.go | 132 ++++++++
lib/test_ca_cert.pem | 18 ++
lib/test_chain.pem | 36 +++
lib/test_leaf_cert.pem | 18 ++
tag.sh | 2 +-
24 files changed, 1181 insertions(+), 798 deletions(-)
create mode 100644 auth_config/auth_config_example.json
create mode 100644 auth_config/auth_config_schema.json
create mode 100644 auth_config/command_config.go
delete mode 100644 auth_providers/active_directory/ad_auth.go
delete mode 100644 auth_providers/active_directory/ad_auth_test.go
create mode 100644 auth_providers/auth_basic_test.go
create mode 100644 auth_providers/auth_core_test.go
create mode 100644 auth_providers/auth_oauth_test.go
delete mode 100644 auth_providers/keycloak/keycloak_auth_certificate.go
delete mode 100644 auth_providers/keycloak/keycloak_auth_client_base.go
delete mode 100644 auth_providers/keycloak/keycloak_auth_client_credentials.go
delete mode 100644 auth_providers/keycloak/keycloak_auth_client_credentials_test.go
create mode 100644 lib/main.go
create mode 100644 lib/test_ca_cert.pem
create mode 100644 lib/test_chain.pem
create mode 100644 lib/test_leaf_cert.pem
diff --git a/.github/config/variables.tf b/.github/config/variables.tf
index 8c52d88..c61463b 100644
--- a/.github/config/variables.tf
+++ b/.github/config/variables.tf
@@ -27,12 +27,12 @@ variable "keyfactor_client_secret_11_5_0" {
variable "keyfactor_hostname_11_5_0_KC" {
description = "The hostname of the Keyfactor instance"
type = string
- default = "pkiaas-spb.eastus2.cloudapp.azure.com"
+ default = "int-oidc-lab.eastus2.cloudapp.azure.com"
}
variable "keyfactor_auth_hostname_11_5_0_KC" {
description = "The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token"
type = string
- default = "pkiaas-spb.eastus2.cloudapp.azure.com"
+ default = "int-oidc-lab.eastus2.cloudapp.azure.com"
}
diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml
index 97011fc..01ddd34 100644
--- a/.github/workflows/keyfactor-starter-workflow.yml
+++ b/.github/workflows/keyfactor-starter-workflow.yml
@@ -11,8 +11,7 @@ on:
jobs:
call-starter-workflow:
- uses: keyfactor/actions/.github/workflows/starter.yml@v2
- needs: get-versions
+ uses: keyfactor/actions/.github/workflows/starter.yml@v3
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
diff --git a/auth_config/auth_config_example.json b/auth_config/auth_config_example.json
new file mode 100644
index 0000000..d2f4fac
--- /dev/null
+++ b/auth_config/auth_config_example.json
@@ -0,0 +1,42 @@
+{
+ "servers": {
+ "default_profile": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "auth_hostname": "idp.keyfactor.command.kfdelivery.com",
+ "auth_port": 8444,
+ "username": "keyfactor",
+ "password": "password",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "domain": "command",
+ "api_path": "KeyfactorAPI",
+ "auth_provider": {
+ "type": "azid",
+ "profile": "azure",
+ "parameters": {
+ "secret_name": "command-config-azure",
+ "vault_name": "keyfactor-secrets"
+ }
+ }
+ },
+ "alternative_profile": {
+ "host": "keyfactor2.command.kfdelivery.com",
+ "auth_hostname": "idp.keyfactor2.command.kfdelivery.com",
+ "auth_port": 8444,
+ "username": "keyfactor2",
+ "password": "password2",
+ "client_id": "client-id2",
+ "client_secret": "client-secret2",
+ "domain": "command",
+ "api_path": "KeyfactorAPI",
+ "auth_provider": {
+ "type": "azid",
+ "profile": "azure",
+ "parameters": {
+ "secret_name": "command-config-azure2",
+ "vault_name": "keyfactor-secrets"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/auth_config/auth_config_schema.json b/auth_config/auth_config_schema.json
new file mode 100644
index 0000000..7b9bfc9
--- /dev/null
+++ b/auth_config/auth_config_schema.json
@@ -0,0 +1,110 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "servers": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "type": "object",
+ "properties": {
+ "host": {
+ "type": "string"
+ },
+ "auth_hostname": {
+ "type": "string"
+ },
+ "auth_port": {
+ "type": "integer"
+ },
+ "username": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "client_id": {
+ "type": "string"
+ },
+ "client_secret": {
+ "type": "string"
+ },
+ "domain": {
+ "type": "string"
+ },
+ "api_path": {
+ "type": "string"
+ },
+ "auth_provider": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "profile": {
+ "type": "string"
+ },
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "secret_name": {
+ "type": "string"
+ },
+ "vault_name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "secret_name",
+ "vault_name"
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "profile",
+ "parameters"
+ ]
+ }
+ },
+ "oneOf": [
+ {
+ "required": ["username", "password"],
+ "not": {
+ "required": ["client_id", "client_secret"]
+ }
+ },
+ {
+ "required": ["client_id", "client_secret"],
+ "not": {
+ "required": ["username", "password"]
+ }
+ }
+ ],
+ "if": {
+ "required": ["auth_provider"]
+ },
+ "then": {
+ "required": ["auth_provider"]
+ },
+ "else": {
+ "if": {
+ "required": ["client_id", "client_secret"]
+ },
+ "then": {
+ "required": ["auth_hostname", "host"]
+ },
+ "else": {
+ "required": ["host"]
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "servers"
+ ],
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
new file mode 100644
index 0000000..4fb5bd4
--- /dev/null
+++ b/auth_config/command_config.go
@@ -0,0 +1,173 @@
+package authconfig
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "gopkg.in/yaml.v2"
+)
+
+// Server represents the server configuration for authentication.
+type Server struct {
+ Host string `json:"host,omitempty" yaml:"host,omitempty"` // Host is the Command server DNS name or IP address.
+ Port int `json:"port,omitempty" yaml:"port,omitempty"` // Port is the Command server port.
+ AuthHostname string `json:"auth_hostname,omitempty" yaml:"auth_hostname,omitempty"` // AuthHostname is the authentication hostname.
+ AuthPort int `json:"auth_port,omitempty" yaml:"auth_port,omitempty"` // AuthPort is the authentication port.
+ Username string `json:"username,omitempty" yaml:"username,omitempty"` // Username is the username for authentication.
+ Password string `json:"password,omitempty" yaml:"password,omitempty"` // Password is the password for authentication.
+ ClientID string `json:"client_id,omitempty" yaml:"client_id,omitempty"` // ClientID is the client ID for OAuth.
+ ClientSecret string `json:"client_secret,omitempty" yaml:"client_secret,omitempty"` // ClientSecret is the client secret for OAuth.
+ Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` // Domain is the domain for authentication.
+ APIPath string `json:"api_path,omitempty" yaml:"api_path,omitempty"` // APIPath is the API path.
+ AuthProvider AuthProvider `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` // AuthProvider contains the authentication provider details.
+ SkipTLSVerify bool `json:"skip_tls_verify,omitempty" yaml:"skip_tls_verify,omitempty"` // TLSVerify determines whether to verify the TLS certificate.
+ CACertPath string `json:"ca_cert_path,omitempty" yaml:"ca_cert_path,
+omitempty"` // CACertPath is the path to the CA certificate to trust.
+}
+
+// AuthProvider represents the authentication provider configuration.
+type AuthProvider struct {
+ Type string `json:"type,omitempty" yaml:"type,omitempty"` // Type is the type of authentication provider.
+ Profile string `json:"profile,omitempty" yaml:"profile,omitempty"` // Profile is the profile of the authentication provider.
+ Parameters interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` // Parameters are additional parameters for the authentication provider.
+}
+
+// Config represents the overall configuration structure.
+type Config struct {
+ Servers map[string]Server `json:"servers,omitempty" yaml:"servers,omitempty"` // Servers is a map of server configurations.
+}
+
+// UnmarshalJSON unmarshals the JSON data into the Config struct.
+//func (c *Config) UnmarshalJSON(data []byte) error {
+// return json.Unmarshal(data, c)
+//}
+
+// UnmarshalYAML unmarshals the YAML data into the Config struct.
+//func (c *Config) UnmarshalYAML(data []byte) error {
+// return yaml.Unmarshal(data, c)
+//}
+
+// ReadServerFromJSON reads a Server configuration from a JSON file.
+func ReadServerFromJSON(filePath string) (*Server, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var server Server
+ decoder := json.NewDecoder(file)
+ if err := decoder.Decode(&server); err != nil {
+ return nil, err
+ }
+
+ return &server, nil
+}
+
+// WriteServerToJSON writes a Server configuration to a JSON file.
+func WriteServerToJSON(filePath string, server *Server) error {
+ file, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ encoder := json.NewEncoder(file)
+ encoder.SetIndent("", " ")
+ if err := encoder.Encode(server); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReadServerFromYAML reads a Server configuration from a YAML file.
+func ReadServerFromYAML(filePath string) (*Server, error) {
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ var server Server
+ if err := yaml.Unmarshal(file, &server); err != nil {
+ return nil, err
+ }
+
+ return &server, nil
+}
+
+// WriteServerToYAML writes a Server configuration to a YAML file.
+func WriteServerToYAML(filePath string, server *Server) error {
+ data, err := yaml.Marshal(server)
+ if err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(filePath, data, 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WriteConfigToJSON writes a Config configuration to a JSON file.
+func WriteConfigToJSON(filePath string, config *Config) error {
+ file, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ encoder := json.NewEncoder(file)
+ encoder.SetIndent("", " ")
+ if err := encoder.Encode(config); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WriteConfigToYAML writes a Config configuration to a YAML file.
+func WriteConfigToYAML(filePath string, config *Config) error {
+ data, err := yaml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(filePath, data, 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MergeConfigFromFile merges the configuration from a file into the existing Config.
+func MergeConfigFromFile(filePath string, config *Config) error {
+ // Read the file content
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ return fmt.Errorf("failed to read config file: %w", err)
+ }
+
+ // Determine the file type (JSON or YAML) and unmarshal accordingly
+ var tempConfig Config
+ if json.Valid(data) {
+ if err := json.Unmarshal(data, &tempConfig); err != nil {
+ return fmt.Errorf("failed to unmarshal JSON config: %w", err)
+ }
+ } else {
+ if err := yaml.Unmarshal(data, &tempConfig); err != nil {
+ return fmt.Errorf("failed to unmarshal YAML config: %w", err)
+ }
+ }
+
+ // Merge the temporary config into the existing config
+ for key, server := range tempConfig.Servers {
+ if _, exists := config.Servers[key]; !exists {
+ config.Servers[key] = server
+ }
+ }
+
+ return nil
+}
diff --git a/auth_providers/active_directory/ad_auth.go b/auth_providers/active_directory/ad_auth.go
deleted file mode 100644
index 1c6a559..0000000
--- a/auth_providers/active_directory/ad_auth.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2024 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 active_directory
-
-import (
- "encoding/base64"
- "fmt"
- "os"
- "strings"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
-)
-
-const (
- EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
- EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
- EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
-)
-
-// CommandAuthConfigActiveDirectory represents the configuration needed for Active Directory authentication.
-// It embeds CommandAuthConfigBasic and adds additional fields specific to Active Directory.
-type CommandAuthConfigActiveDirectory struct {
- // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
- auth_providers.CommandAuthConfigBasic
-
- // Domain is the domain of the Active Directory used to authenticate to Keyfactor Command API
- Domain string `json:"domain"`
-}
-
-// Authenticate performs the authentication process for Active Directory.
-// It validates the authentication configuration, generates the authentication header, and calls the basic authentication method.
-func (c *CommandAuthConfigActiveDirectory) Authenticate() error {
- cErr := c.ValidateAuthConfig()
- if cErr != nil {
- return cErr
- }
-
- c.AuthHeader = fmt.Sprintf("Basic %s", c.getBasicAuthHeader())
-
- aErr := c.CommandAuthConfigBasic.Authenticate()
- if aErr != nil {
- return aErr
- }
-
- return nil
-}
-
-// getBasicAuthHeader generates the basic authentication header value.
-// It combines the username, domain, and password with a colon separator, and encodes the resulting string in base64.
-func (c *CommandAuthConfigActiveDirectory) getBasicAuthHeader() string {
- authStr := fmt.Sprintf("%s@%s:%s", c.Username, c.Domain, c.Password)
- return base64.StdEncoding.EncodeToString([]byte(authStr))
-}
-
-// parseUsernameDomain parses the username to extract the domain if it's included in the username.
-// It supports two formats: "username@domain" and "domain\username".
-func (c *CommandAuthConfigActiveDirectory) parseUsernameDomain() error {
- domainErr := fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
- if strings.Contains(c.Username, "@") {
- dSplit := strings.Split(c.Username, "@")
- if len(dSplit) != 2 {
- return domainErr
- }
- c.Username = dSplit[0] // remove domain from username
- c.Domain = dSplit[1]
- } else if strings.Contains(c.Username, "\\") {
- dSplit := strings.Split(c.Username, "\\")
- if len(dSplit) != 2 {
- return domainErr
- }
- c.Domain = dSplit[0]
- c.Username = dSplit[1] // remove domain from username
- }
-
- return nil
-}
-
-// ValidateAuthConfig validates the authentication configuration for Active Directory.
-// It checks the username, domain, and password, and retrieves them from environment variables if they're not set.
-func (c *CommandAuthConfigActiveDirectory) ValidateAuthConfig() error {
- cErr := c.CommandAuthConfigBasic.ValidateAuthConfig()
- if cErr != nil {
- return cErr
- }
-
- if c.Username == "" {
- if username, ok := os.LookupEnv(EnvKeyfactorUsername); ok {
- c.Username = username
- } else {
- return fmt.Errorf("username or environment variable %s is required", EnvKeyfactorUsername)
- }
- }
-
- domainErr := c.parseUsernameDomain()
- if domainErr != nil {
- return domainErr
-
- }
-
- if c.Password == "" {
- if password, ok := os.LookupEnv(EnvKeyfactorPassword); ok {
- c.Password = password
- } else {
- return fmt.Errorf("password or environment variable %s is required", EnvKeyfactorPassword)
- }
- }
-
- if c.Domain == "" {
- if domain, ok := os.LookupEnv(EnvKeyfactorDomain); ok {
- c.Domain = domain
- } else {
- return domainErr
- }
- }
-
- return nil
-}
diff --git a/auth_providers/active_directory/ad_auth_test.go b/auth_providers/active_directory/ad_auth_test.go
deleted file mode 100644
index 50d06b5..0000000
--- a/auth_providers/active_directory/ad_auth_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2024 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 active_directory
-
-import (
- "fmt"
- "os"
- "testing"
-)
-
-const (
- TestEnvIsADAuth = "TEST_KEYFACTOR_AD_AUTH"
-)
-
-func TestCommandAuthActiveDirectoryCredentials_AuthEnvironment(t *testing.T) {
-
- if !checkAuthEnvADCreds() {
- msg := "Skipping test because Keyfactor Command environment is not authenticated with Active Directory credentials"
- t.Log(msg)
- t.Skip(msg)
- return
- }
- // Create a new CommandAuthActiveDirectoryCredentials instance
- c := &CommandAuthConfigActiveDirectory{} //Used environment configuration
-
- // Call the Authenticate method
- err := c.Authenticate()
- if err != nil {
- t.Errorf("Authenticate() error = %v", err)
- return
- }
-
- // Check that the AuthHeader was set correctly
- expectedAuthHeader := fmt.Sprintf("Basic %s", c.getBasicAuthHeader())
- if c.AuthHeader != expectedAuthHeader {
- t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
- }
-}
-
-func checkAuthEnvADCreds() bool {
- if isAdAuth, ok := os.LookupEnv(TestEnvIsADAuth); ok {
- if isAdAuth == "true" || isAdAuth == "1" {
- return true
- }
- }
- return false
-}
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 8a09fa3..e308535 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -18,47 +18,25 @@ import (
"encoding/base64"
"fmt"
"net/http"
+ "os"
)
+const (
+ EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
+ EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
+)
+
+// Basic Authenticator
var _ Authenticator = &BasicAuthAuthenticator{}
+// BasicAuthAuthenticator is an Authenticator that uses Basic Auth for authentication.
type BasicAuthAuthenticator struct {
- client *http.Client
-}
-
-func BasicAuthTransport(username, password string) *http.Client {
- // Encode the username and password in Base64
- auth := username + ":" + password
- encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
-
- // Create a custom RoundTripper
- transport := &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- // You can customize other transport settings here
- }
-
- return &http.Client{
- Transport: roundTripperFunc(
- func(req *http.Request) (*http.Response, error) {
- // Add the Authorization header to the request
- req.Header.Set("Authorization", "Basic "+encodedAuth)
-
- // Forward the request to the actual transport
- return transport.RoundTrip(req)
- },
- ),
- }
-}
-
-// roundTripperFunc is a helper type to create a custom RoundTripper
-type roundTripperFunc func(req *http.Request) (*http.Response, error)
-
-func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
- return f(req)
+ Client *http.Client
}
+// GetHttpClient returns the http client
func (b *BasicAuthAuthenticator) GetHttpClient() (*http.Client, error) {
- return b.client, nil
+ return b.Client, nil
}
// CommandAuthConfigBasic represents the base configuration needed for authentication to Keyfactor Command API.
@@ -67,55 +45,112 @@ type CommandAuthConfigBasic struct {
CommandAuthConfig
// Username is the username to be used for authentication to Keyfactor Command API
- Username string `json:"username"`
+ Username string `json:"username,omitempty"`
// Password is the password to be used for authentication to Keyfactor Command API
- Password string `json:"password"`
+ Password string `json:"password,omitempty"`
}
+// NewBasicAuthAuthenticatorBuilder creates a new instance of CommandAuthConfigBasic
func NewBasicAuthAuthenticatorBuilder() *CommandAuthConfigBasic {
return &CommandAuthConfigBasic{}
}
+// WithUsername sets the username for authentication
func (a *CommandAuthConfigBasic) WithUsername(username string) *CommandAuthConfigBasic {
a.Username = username
return a
}
+// WithPassword sets the password for authentication
func (a *CommandAuthConfigBasic) WithPassword(password string) *CommandAuthConfigBasic {
a.Password = password
return a
}
+// GetHttpClient returns the http client
+func (a *CommandAuthConfigBasic) GetHttpClient() (*http.Client, error) {
+ //validate the configuration
+ cErr := a.ValidateAuthConfig()
+ if cErr != nil {
+ return nil, cErr
+ }
+
+ // Encode the username and password in Base64
+ auth := a.Username + ":" + a.Password
+ encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
+
+ // Create a custom RoundTripper
+ transport, tErr := a.CommandAuthConfig.BuildTransport()
+ if tErr != nil {
+ return nil, tErr
+ }
+
+ return &http.Client{
+ Transport: roundTripperFunc(
+ func(req *http.Request) (*http.Response, error) {
+ // Add the Authorization header to the request
+ req.Header.Set("Authorization", "Basic "+encodedAuth)
+
+ // Forward the request to the actual transport
+ return transport.RoundTrip(req)
+ },
+ ),
+ }, nil
+}
+
+// Build creates a new instance of BasicAuthAuthenticator
func (a *CommandAuthConfigBasic) Build() (Authenticator, error) {
- client := BasicAuthTransport(a.Username, a.Password)
+ client, cErr := a.GetHttpClient()
+ if cErr != nil {
+ return nil, cErr
+ }
a.HttpClient = client
- return &BasicAuthAuthenticator{client: client}, nil
+ return &BasicAuthAuthenticator{Client: client}, nil
}
+// ValidateAuthConfig validates the configuration
func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
+ serverConfig, _ := a.CommandAuthConfig.LoadConfig(
+ a.CommandAuthConfig.ConfigProfile,
+ a.CommandAuthConfig.ConfigFilePath,
+ )
if a.Username == "" {
- return fmt.Errorf("username is required")
+ if username, ok := os.LookupEnv(EnvKeyfactorUsername); ok {
+ a.Username = username
+ } else {
+ if serverConfig != nil && serverConfig.Username != "" {
+ a.Username = serverConfig.Username
+ } else {
+ return fmt.Errorf("username or environment variable %s is required", EnvKeyfactorUsername)
+ }
+ }
}
if a.Password == "" {
- return fmt.Errorf("password is required")
+ if password, ok := os.LookupEnv(EnvKeyfactorPassword); ok {
+ a.Password = password
+ } else {
+ if serverConfig != nil && serverConfig.Password != "" {
+ a.Password = serverConfig.Password
+ } else {
+ return fmt.Errorf("password or environment variable %s is required", EnvKeyfactorPassword)
+ }
+ }
}
return a.CommandAuthConfig.ValidateAuthConfig()
}
+// Authenticate authenticates the user
func (a *CommandAuthConfigBasic) Authenticate() error {
cErr := a.ValidateAuthConfig()
if cErr != nil {
return cErr
}
- //basicAuth := fmt.Sprintf("%s:%s", c.Username, c.Password)
- //basicAuth = base64.StdEncoding.EncodeToString([]byte(basicAuth))
- //c.AuthHeader = fmt.Sprintf("Basic %s", basicAuth)
- // create oauth client
+ // create oauth Client
authy, err := NewBasicAuthAuthenticatorBuilder().
WithUsername(a.Username).
WithPassword(a.Password).
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
new file mode 100644
index 0000000..50dcff9
--- /dev/null
+++ b/auth_providers/auth_basic_test.go
@@ -0,0 +1,77 @@
+package auth_providers_test
+
+import (
+ "net/http"
+ "os"
+ "testing"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
+)
+
+func TestBasicAuthAuthenticator_GetHttpClient(t *testing.T) {
+ auth := &auth_providers.BasicAuthAuthenticator{
+ 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 TestCommandAuthConfigBasic_ValidateAuthConfig(t *testing.T) {
+ config := &auth_providers.CommandAuthConfigBasic{
+ Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
+ Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
+ }
+
+ err := config.ValidateAuthConfig()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestCommandAuthConfigBasic_GetHttpClient(t *testing.T) {
+ config := &auth_providers.CommandAuthConfigBasic{
+ Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
+ Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
+ }
+
+ 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 TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
+ config := &auth_providers.CommandAuthConfigBasic{}
+
+ err := config.Authenticate()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestCommandAuthConfigBasic_Build(t *testing.T) {
+ config := &auth_providers.CommandAuthConfigBasic{
+ Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
+ Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
+ }
+
+ 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")
+ }
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 4394a5d..ad49bae 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -23,22 +23,30 @@ import (
"io"
"net/http"
"os"
+ "path/filepath"
+ "strconv"
"strings"
"time"
+
+ authconfig "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
)
const (
- DefaultCommandPort = "443"
- DefaultCommandAPIPath = "KeyfactorAPI"
- DefaultAPIVersion = "1"
- DefaultAPIClientName = "APIClient"
- DefaultProductVersion = "10.5.0.0"
- EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
- EnvKeyfactorPort = "KEYFACTOR_PORT"
- EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
- EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
- EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
- EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
+ DefaultCommandPort = 443
+ DefaultCommandAPIPath = "KeyfactorAPI"
+ DefaultAPIVersion = "1"
+ DefaultAPIClientName = "APIClient"
+ DefaultProductVersion = "10.5.0.0"
+ DefaultConfigFilePath = "~/.keyfactor/command_config.json"
+ DefaultClientTimeout = 60
+
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+ EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
+ EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
+ EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
+ EnvKeyfactorClientTimeout = "KEYFACTOR_CLIENT_TIMEOUT"
)
// Authenticator is an interface for authentication to Keyfactor Command API.
@@ -46,11 +54,28 @@ type Authenticator interface {
GetHttpClient() (*http.Client, error)
}
+// roundTripperFunc is a helper type to create a custom RoundTripper
+type roundTripperFunc func(req *http.Request) (*http.Response, error)
+
+// RoundTrip executes a single HTTP transaction
+func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+ return f(req)
+}
+
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
type CommandAuthConfig struct {
// ConfigType is the type of configuration
ConfigType string `json:"config_type"`
+ //ConfigProfile is the profile of the configuration
+ ConfigProfile string
+
+ //ConfigFilePath is the path to the configuration file
+ ConfigFilePath string
+
+ // FileConfig
+ FileConfig *authconfig.Server
+
// AuthHeader is the header to be used for authentication to Keyfactor Command API
AuthHeader string `json:"auth_header"`
@@ -58,7 +83,7 @@ type CommandAuthConfig struct {
CommandHostName string `json:"command_host_name"`
// CommandPort is the port of the Keyfactor Command API
- CommandPort string `json:"command_port"`
+ CommandPort int `json:"command_port"`
// CommandAPIPath is the path of the Keyfactor Command API, default is "KeyfactorAPI"
CommandAPIPath string `json:"command_api_path"`
@@ -72,52 +97,91 @@ type CommandAuthConfig struct {
// SkipVerify is a flag to skip verification of the server's certificate chain and host name. Default is false.
SkipVerify bool `json:"skip_verify"`
- // HttpClient is the http client to be used for authentication to Keyfactor Command API
+ // HttpClientTimeout is the timeout for the http Client
+ HttpClientTimeout int `json:"client_timeout"`
+
+ // HttpClient is the http Client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
}
+// WithCommandHostName sets the hostname for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithCommandHostName(hostName string) *CommandAuthConfig {
c.CommandHostName = hostName
return c
}
-func (c *CommandAuthConfig) WithCommandPort(port string) *CommandAuthConfig {
+// WithCommandPort sets the port for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) WithCommandPort(port int) *CommandAuthConfig {
c.CommandPort = port
return c
}
+// WithCommandAPIPath sets the API path for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithCommandAPIPath(apiPath string) *CommandAuthConfig {
c.CommandAPIPath = apiPath
return c
}
+// WithCommandCACert sets the CA certificate for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithCommandCACert(caCert string) *CommandAuthConfig {
c.CommandCACert = caCert
return c
}
+// WithSkipVerify sets the flag to skip verification of the server's certificate chain and host name.
func (c *CommandAuthConfig) WithSkipVerify(skipVerify bool) *CommandAuthConfig {
c.SkipVerify = skipVerify
return c
}
+// WithHttpClient sets the http Client for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithHttpClient(client *http.Client) *CommandAuthConfig {
c.HttpClient = client
return c
}
+// WithConfigFile sets the configuration file for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) WithConfigFile(configFilePath string) *CommandAuthConfig {
+
+ if c.ConfigProfile == "" {
+ c.ConfigProfile = "default"
+ }
+
+ c.ConfigFilePath = configFilePath
+ return c
+}
+
+// WithConfigProfile sets the configuration profile for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) WithConfigProfile(profile string) *CommandAuthConfig {
+ c.ConfigProfile = profile
+ return c
+}
+
+// WithClientTimeout sets the timeout for the http Client.
+func (c *CommandAuthConfig) WithClientTimeout(timeout int) *CommandAuthConfig {
+ c.HttpClientTimeout = timeout
+ return c
+}
+
// ValidateAuthConfig validates the authentication configuration for Keyfactor Command API.
func (c *CommandAuthConfig) ValidateAuthConfig() error {
if c.CommandHostName == "" {
if hostName, ok := os.LookupEnv(EnvKeyfactorHostName); ok {
c.CommandHostName = hostName
} else {
- return fmt.Errorf("command_host_name or environment variable %s is required", EnvKeyfactorHostName)
+ if c.FileConfig != nil {
+ c.CommandHostName = c.FileConfig.Host
+ } else {
+ return fmt.Errorf("command_host_name or environment variable %s is required", EnvKeyfactorHostName)
+ }
}
}
- if c.CommandPort == "" {
+ if c.CommandPort <= 0 {
if port, ok := os.LookupEnv(EnvKeyfactorPort); ok {
- c.CommandPort = port
+ configPort, pErr := strconv.Atoi(port)
+ if pErr == nil {
+ c.CommandPort = configPort
+ }
} else {
c.CommandPort = DefaultCommandPort
}
@@ -129,6 +193,16 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.CommandAPIPath = DefaultCommandAPIPath
}
}
+ if c.HttpClientTimeout <= 0 {
+ if timeout, ok := os.LookupEnv(EnvKeyfactorClientTimeout); ok {
+ configTimeout, tErr := strconv.Atoi(timeout)
+ if tErr == nil {
+ c.HttpClientTimeout = configTimeout
+ }
+ } else {
+ c.HttpClientTimeout = DefaultClientTimeout
+ }
+ }
c.SetClient(nil)
// check for skip verify in environment
@@ -151,7 +225,52 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
return nil
}
-// SetClient sets the http client for authentication to Keyfactor Command API.
+// BuildTransport creates a custom http Transport for authentication to Keyfactor Command API.
+func (c *CommandAuthConfig) BuildTransport() (*http.Transport, error) {
+ output := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: &tls.Config{
+ Renegotiation: tls.RenegotiateOnceAsClient,
+ },
+ TLSHandshakeTimeout: 10 * time.Second,
+ }
+ if c.SkipVerify {
+ output.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ }
+
+ // Load the system certs
+ if c.CommandCACert != "" {
+ rootCAs, pErr := x509.SystemCertPool()
+ if pErr != nil {
+ return nil, pErr
+ }
+ if rootCAs == nil {
+ rootCAs = x509.NewCertPool()
+ }
+
+ // check if CommandCACert is a file
+ if _, err := os.Stat(c.CommandCACert); err == nil {
+ cert, ioErr := os.ReadFile(c.CommandCACert)
+ if ioErr != nil {
+ return nil, ioErr
+ }
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
+ return nil, fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ } else {
+ // Append your custom cert to the pool
+ if ok := rootCAs.AppendCertsFromPEM([]byte(c.CommandCACert)); !ok {
+ return nil, fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ }
+
+ output.TLSClientConfig.RootCAs = rootCAs
+ }
+ return output, nil
+}
+
+// SetClient sets the http Client for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) SetClient(client *http.Client) *http.Client {
if client != nil {
c.HttpClient = client
@@ -162,7 +281,7 @@ func (c *CommandAuthConfig) SetClient(client *http.Client) *http.Client {
return c.HttpClient
}
-// updateCACerts updates the CA certs for the http client.
+// updateCACerts updates the CA certs for the http Client.
func (c *CommandAuthConfig) updateCACerts() error {
// check if CommandCACert is set
if c.CommandCACert == "" {
@@ -174,7 +293,7 @@ func (c *CommandAuthConfig) updateCACerts() error {
}
}
- // ensure client is set
+ // ensure Client is set
c.SetClient(nil)
// Load the system certs
@@ -203,7 +322,7 @@ func (c *CommandAuthConfig) updateCACerts() error {
}
}
- // Trust the augmented cert pool in our client
+ // Trust the augmented cert pool in our Client
c.HttpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
@@ -247,7 +366,7 @@ func (c *CommandAuthConfig) Authenticate() error {
req.Header.Set(key, value)
}
- c.HttpClient.Timeout = 60 * time.Second
+ c.HttpClient.Timeout = time.Duration(c.HttpClientTimeout) * time.Second
cResp, cErr := c.HttpClient.Do(req)
if cErr != nil {
@@ -294,6 +413,24 @@ func (c *CommandAuthConfig) Authenticate() error {
}
+// LoadCACertificates loads the custom CA certificates from a file.
+func LoadCACertificates(certFile string) (*x509.CertPool, error) {
+ // Read the file containing the custom CA certificate
+ certBytes, err := os.ReadFile(certFile)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create a new CertPool and append the custom CA certificate
+ certPool := x509.NewCertPool()
+ if ok := certPool.AppendCertsFromPEM(certBytes); !ok {
+ return nil, err
+ }
+
+ return certPool, nil
+}
+
+// FindCACertificate reads the CA certificate from a file and returns a slice of x509.Certificate.
func FindCACertificate(caCertificatePath string) ([]*x509.Certificate, error) {
if caCertificatePath == "" {
return nil, nil
@@ -326,6 +463,7 @@ func FindCACertificate(caCertificatePath string) ([]*x509.Certificate, error) {
return caChain, nil
}
+// DecodePEMBytes decodes the PEM encoded bytes into a slice of PEM blocks.
func DecodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
var privKey []byte
var certificates []*pem.Block
@@ -342,3 +480,64 @@ func DecodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
}
return certificates, privKey, nil
}
+
+// LoadConfig loads the configuration file and returns the server configuration.
+func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string) (*authconfig.Server, error) {
+ if configFilePath == "" {
+ if c.ConfigFilePath != "" {
+ configFilePath = c.ConfigFilePath
+ } else {
+ configFilePath = DefaultConfigFilePath
+ }
+ }
+ expandedPath, err := expandPath(configFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ file, err := os.Open(expandedPath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var config authconfig.Config
+ decoder := json.NewDecoder(file)
+ if jErr := decoder.Decode(&config); jErr != nil {
+ return nil, jErr
+ }
+
+ if profile == "" {
+ if c.ConfigProfile != "" {
+ profile = c.ConfigProfile
+ } else {
+ profile = "default"
+ }
+ }
+
+ server, ok := config.Servers[profile]
+ if !ok {
+ return nil, fmt.Errorf("profile %s not found in config file", profile)
+ }
+
+ c.FileConfig = &server
+ c.CommandHostName = server.Host
+ c.CommandPort = server.Port
+ c.CommandAPIPath = server.APIPath
+ c.CommandCACert = server.CACertPath // TODO: Implement CACert in config file
+ c.SkipVerify = server.SkipTLSVerify
+
+ return &server, nil
+}
+
+// expandPath expands the path to include the user's home directory.
+func expandPath(path string) (string, error) {
+ if path[:2] == "~/" {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(home, path[2:]), nil
+ }
+ return path, nil
+}
diff --git a/auth_providers/auth_core_test.go b/auth_providers/auth_core_test.go
new file mode 100644
index 0000000..5a3e938
--- /dev/null
+++ b/auth_providers/auth_core_test.go
@@ -0,0 +1,90 @@
+package auth_providers_test
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
+)
+
+func TestCommandAuthConfig_ValidateAuthConfig(t *testing.T) {
+ config := &auth_providers.CommandAuthConfig{
+ CommandHostName: "test-host",
+ CommandPort: 443,
+ CommandAPIPath: "KeyfactorAPI",
+ }
+
+ err := config.ValidateAuthConfig()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestCommandAuthConfig_BuildTransport(t *testing.T) {
+ config := &auth_providers.CommandAuthConfig{
+ CommandHostName: "test-host",
+ CommandPort: 443,
+ CommandAPIPath: "KeyfactorAPI",
+ }
+
+ transport, err := config.BuildTransport()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+
+ if transport == nil {
+ t.Fatalf("expected a non-nil http.Transport")
+ }
+}
+
+func TestCommandAuthConfig_SetClient(t *testing.T) {
+ config := &auth_providers.CommandAuthConfig{}
+
+ client := &http.Client{}
+ config.SetClient(client)
+
+ if config.HttpClient != client {
+ t.Fatalf("expected HttpClient to be set")
+ }
+}
+
+func TestCommandAuthConfig_Authenticate(t *testing.T) {
+ config := &auth_providers.CommandAuthConfig{
+ CommandHostName: "test-host",
+ CommandPort: 443,
+ CommandAPIPath: "KeyfactorAPI",
+ }
+
+ err := config.Authenticate()
+ if err == nil {
+ t.Fatalf("expected an error, got nil")
+ }
+}
+
+func TestLoadCACertificates(t *testing.T) {
+ _, err := auth_providers.LoadCACertificates("../lib/test_ca_cert.pem")
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestFindCACertificate(t *testing.T) {
+ _, err := auth_providers.FindCACertificate("../lib/test_chain.pem")
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestDecodePEMBytes(t *testing.T) {
+ pemData := []byte(`-----BEGIN CERTIFICATE-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Q2+1+2+1+2+1+2+1+2+
+-----END CERTIFICATE-----`)
+ blocks, _, err := auth_providers.DecodePEMBytes(pemData)
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+
+ if len(blocks) == 0 {
+ t.Fatalf("expected non-zero blocks")
+ }
+}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index df51e35..ace3776 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -2,7 +2,6 @@ package auth_providers
import (
"context"
- "crypto/tls"
"crypto/x509"
"fmt"
"net/http"
@@ -19,32 +18,32 @@ const (
// DefaultTokenPrefix is the default token prefix for Keyfactor authentication headers
DefaultTokenPrefix = "Bearer"
- // EnvKeyfactorClientID is the environment variable used to set the client ID for oauth client credentials authentication
+ // EnvKeyfactorClientID is the environment variable used to set the Client ID for oauth Client credentials authentication
EnvKeyfactorClientID = "KEYFACTOR_AUTH_CLIENT_ID"
- // EnvKeyfactorClientSecret is the environment variable used to set the client secret for oauth client credentials authentication
+ // EnvKeyfactorClientSecret is the environment variable used to set the Client secret for oauth Client credentials authentication
EnvKeyfactorClientSecret = "KEYFACTOR_AUTH_CLIENT_SECRET"
- // EnvKeyfactorAuthTokenURL EnvCommandTokenURL is the environment variable used to set the token URL for oauth client credentials authentication
+ // EnvKeyfactorAuthTokenURL EnvCommandTokenURL is the environment variable used to set the token URL for oauth Client credentials authentication
EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
- // EnvKeyfactorAccessToken is the environment variable used to set the access token for oauth client credentials authentication
+ // EnvKeyfactorAccessToken is the environment variable used to set the access token for oauth Client credentials authentication
EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
- // EnvKeyfactorAuthAudience is the environment variable used to set the audience for oauth client credentials
+ // EnvKeyfactorAuthAudience is the environment variable used to set the audience for oauth Client credentials
//authentication
EnvKeyfactorAuthAudience = "KEYFACTOR_AUTH_AUDIENCE"
- // EnvKeyfactorAuthScopes is the environment variable used to set the scopes for oauth client credentials authentication
+ // EnvKeyfactorAuthScopes is the environment variable used to set the scopes for oauth Client credentials authentication
EnvKeyfactorAuthScopes = "KEYFACTOR_AUTH_SCOPES"
- // EnvKeyfactorAuthHostname is the environment variable used to set the hostname for oauth client credentials authentication
+ // EnvKeyfactorAuthHostname is the environment variable used to set the hostname for oauth Client credentials authentication
EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
- // EnvKeyfactorAuthPort is the environment variable used to set the port for oauth client credentials authentication
+ // EnvKeyfactorAuthPort is the environment variable used to set the port for oauth Client credentials authentication
EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
- // EnvAuthCACert is a path to a CA certificate for the OAuth client credentials authentication
+ // EnvAuthCACert is a path to a CA certificate for the OAuth Client credentials authentication
EnvAuthCACert = "KEYFACTOR_AUTH_CA_CERT"
)
@@ -53,64 +52,107 @@ var _ Authenticator = &OAuthAuthenticator{}
// OAuthAuthenticator is an Authenticator that uses OAuth2 for authentication.
type OAuthAuthenticator struct {
- client *http.Client
+ Client *http.Client
}
func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
- return a.client, nil
+ return a.Client, nil
}
type CommandConfigOauth struct {
CommandAuthConfig
- ClientID string `json:"client_id,omitempty"`
- ClientSecret string `json:"client_secret,omitempty"`
- TokenURL string `json:"token_url,omitempty"`
- Audience string `json:"audience,omitempty"`
- Scopes []string `json:"scopes,omitempty"`
- CACertificatePath string `json:"idp_ca_cert_path,omitempty"`
- CACertificates []*x509.Certificate `json:"-"`
+
+ // ClientID is the Client ID for Keycloak authentication
+ ClientID string `json:"client_id,omitempty"`
+
+ // ClientSecret is the Client secret for Keycloak authentication
+ ClientSecret string `json:"client_secret,omitempty"`
+
+ // Audience is the audience for Keycloak authentication
+ Audience string `json:"audience,omitempty"`
+
+ // Scopes is the scopes for Keycloak authentication
+ Scopes []string `json:"scopes,omitempty"`
+
+ // CACertificatePath is the path to the CA certificate for Keycloak authentication
+ CACertificatePath string `json:"idp_ca_cert,omitempty"`
+
+ // CACertificates is the CA certificates for authentication
+ CACertificates []*x509.Certificate `json:"-"`
+
+ // AccessToken is the access token for Keycloak authentication
+ AccessToken string `json:"access_token;omitempty"`
+
+ // RefreshToken is the refresh token for Keycloak authentication
+ RefreshToken string `json:"refresh_token;omitempty"`
+
+ // Expiry is the expiry time of the access token
+ Expiry time.Time `json:"expiry;omitempty"`
+
+ // TokenURL is the token URL for Keycloak authentication
+ TokenURL string `json:"token_url"`
+
+ // AuthPort
+ AuthPort string `json:"auth_port,omitempty"`
+
+ // AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
+ AuthType string `json:"auth_type,omitempty"`
}
+// NewOAuthAuthenticatorBuilder creates a new CommandConfigOauth instance.
func NewOAuthAuthenticatorBuilder() *CommandConfigOauth {
return &CommandConfigOauth{}
}
+// WithClientId sets the Client ID for Keycloak authentication.
func (b *CommandConfigOauth) WithClientId(clientId string) *CommandConfigOauth {
b.ClientID = clientId
return b
}
+// WithClientSecret sets the Client secret for Keycloak authentication.
func (b *CommandConfigOauth) WithClientSecret(clientSecret string) *CommandConfigOauth {
b.ClientSecret = clientSecret
return b
}
+// WithTokenUrl sets the token URL for Keycloak authentication.
func (b *CommandConfigOauth) WithTokenUrl(tokenUrl string) *CommandConfigOauth {
b.TokenURL = tokenUrl
return b
}
+// WithScopes sets the scopes for Keycloak authentication.
func (b *CommandConfigOauth) WithScopes(scopes []string) *CommandConfigOauth {
b.Scopes = scopes
return b
}
+// WithAudience sets the audience for Keycloak authentication.
func (b *CommandConfigOauth) WithAudience(audience string) *CommandConfigOauth {
b.Audience = audience
return b
}
+// WithCACertificatePath sets the CA certificate path for Keycloak authentication.
func (b *CommandConfigOauth) WithCaCertificatePath(caCertificatePath string) *CommandConfigOauth {
b.CACertificatePath = caCertificatePath
return b
}
+// WithCACertificates sets the CA certificates for Keycloak authentication.
func (b *CommandConfigOauth) WithCaCertificates(caCertificates []*x509.Certificate) *CommandConfigOauth {
b.CACertificates = caCertificates
return b
}
-func (b *CommandConfigOauth) Build() (Authenticator, error) {
+func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
+ //validate the configuration
+ cErr := b.ValidateAuthConfig()
+ if cErr != nil {
+ return nil, cErr
+ }
+
config := &clientcredentials.Config{
ClientID: b.ClientID,
ClientSecret: b.ClientSecret,
@@ -118,6 +160,10 @@ func (b *CommandConfigOauth) Build() (Authenticator, error) {
Scopes: b.Scopes,
}
+ if b.Scopes == nil || len(b.Scopes) == 0 {
+ b.Scopes = []string{"openid", "profile", "email"}
+ }
+
if b.Audience != "" {
config.EndpointParams = map[string][]string{
"Audience": {
@@ -126,61 +172,49 @@ func (b *CommandConfigOauth) Build() (Authenticator, error) {
}
}
+ transport, tErr := b.BuildTransport()
+ if tErr != nil {
+ return nil, tErr
+ }
+
tokenSource := config.TokenSource(context.Background())
oauthTransport := &oauth2.Transport{
- Base: http.DefaultTransport,
+ Base: transport,
Source: tokenSource,
}
- if b.CACertificates == nil {
- var err error
- b.CACertificates, err = FindCACertificate(b.CACertificatePath)
- if err != nil {
- return nil, fmt.Errorf("failed to find CA certificates: %w", err)
- }
- }
-
- if len(b.CACertificates) > 0 {
- tlsConfig := &tls.Config{
- Renegotiation: tls.RenegotiateOnceAsClient,
- }
-
- tlsConfig.RootCAs = x509.NewCertPool()
- for _, caCert := range b.CACertificates {
- tlsConfig.RootCAs.AddCert(caCert)
- }
+ return &http.Client{
+ Transport: oauthTransport,
+ }, nil
- customTransport := http.DefaultTransport.(*http.Transport).Clone()
- customTransport.TLSClientConfig = tlsConfig
- customTransport.TLSHandshakeTimeout = 10 * time.Second
+}
- // Wrap the custom transport with the oauth2.Transport
- oauthTransport.Base = customTransport
- }
+func (b *CommandConfigOauth) Build() (Authenticator, error) {
- client := &http.Client{
- Transport: oauthTransport,
+ client, cErr := b.GetHttpClient()
+ if cErr != nil {
+ return nil, cErr
}
- return &OAuthAuthenticator{client: client}, nil
+ return &OAuthAuthenticator{Client: client}, nil
}
func (b *CommandConfigOauth) ValidateAuthConfig() error {
if b.ClientID == "" {
- return fmt.Errorf("client ID is required")
+ return fmt.Errorf("Client ID is required")
}
if b.ClientSecret == "" {
- return fmt.Errorf("client secret is required")
+ return fmt.Errorf("Client secret is required")
}
if b.TokenURL == "" {
return fmt.Errorf("token URL is required")
}
- if len(b.Scopes) == 0 {
- return fmt.Errorf("at least one scope is required")
- }
+ //if len(b.Scopes) == 0 {
+ // return fmt.Errorf("at least one scope is required")
+ //}
return b.CommandAuthConfig.ValidateAuthConfig()
}
@@ -193,7 +227,7 @@ func (b *CommandConfigOauth) Authenticate() error {
return vErr
}
- // create oauth client
+ // create oauth Client
oauthy, err := NewOAuthAuthenticatorBuilder().
WithClientId(b.ClientID).
WithClientSecret(b.ClientSecret).
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
new file mode 100644
index 0000000..5b17147
--- /dev/null
+++ b/auth_providers/auth_oauth_test.go
@@ -0,0 +1,87 @@
+package auth_providers_test
+
+import (
+ "net/http"
+ "os"
+ "testing"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
+)
+
+func TestOAuthAuthenticator_GetHttpClient(t *testing.T) {
+ auth := &auth_providers.OAuthAuthenticator{
+ 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 TestCommandConfigOauth_ValidateAuthConfig(t *testing.T) {
+ config := &auth_providers.CommandConfigOauth{
+ ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
+ ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
+ TokenURL: os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL),
+ }
+
+ err := config.ValidateAuthConfig()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestCommandConfigOauth_GetHttpClient(t *testing.T) {
+ config := &auth_providers.CommandConfigOauth{
+ ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
+ ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
+ TokenURL: os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL),
+ Scopes: []string{"openid", "profile", "email"},
+ }
+
+ 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 TestCommandConfigOauth_Authenticate(t *testing.T) {
+ config := &auth_providers.CommandConfigOauth{
+ ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
+ ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
+ TokenURL: os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL),
+ Scopes: []string{"openid", "profile", "email"},
+ }
+
+ err := config.Authenticate()
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+}
+
+func TestCommandConfigOauth_Build(t *testing.T) {
+ config := &auth_providers.CommandConfigOauth{
+ ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
+ ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
+ TokenURL: os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL),
+ Scopes: []string{"openid", "profile", "email"},
+ }
+
+ 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")
+ }
+}
diff --git a/auth_providers/keycloak/keycloak_auth_certificate.go b/auth_providers/keycloak/keycloak_auth_certificate.go
deleted file mode 100644
index cb44be7..0000000
--- a/auth_providers/keycloak/keycloak_auth_certificate.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2024 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 keycloak
diff --git a/auth_providers/keycloak/keycloak_auth_client_base.go b/auth_providers/keycloak/keycloak_auth_client_base.go
deleted file mode 100644
index 48d94b8..0000000
--- a/auth_providers/keycloak/keycloak_auth_client_base.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2024 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 keycloak
-
-import (
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net/http"
- "os"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
-)
-
-type CommandAuthConfigKeyCloak struct {
- // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
- auth_providers.CommandAuthConfig
-
- // AuthHostName is the hostname of the Keycloak server
- AuthHostName string `json:"auth_host_name"`
-
- // AuthPort is the port of the Keycloak server
- AuthPort string `json:"auth_port"`
-
- // AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
- AuthType string `json:"auth_type"`
-
- // Auth CA Cert is the CA certificate to be used for authentication to Keycloak for use with not widely trusted certificates. This can be a filepath or a string of the certificate in PEM format.
- AuthCACert string `json:"auth_ca_cert"`
-}
-
-// ValidateAuthConfig validates the authentication configuration for Keycloak.
-func (c *CommandAuthConfigKeyCloak) ValidateAuthConfig() error {
- pErr := c.CommandAuthConfig.ValidateAuthConfig()
- if pErr != nil {
- return pErr
- }
-
- if c.AuthHostName == "" {
- if authHostName, ok := os.LookupEnv(EnvKeyfactorAuthHostname); ok {
- c.AuthHostName = authHostName
- } else {
- c.AuthHostName = c.CommandHostName
- }
- }
- if c.AuthPort == "" {
- if port, ok := os.LookupEnv(EnvKeyfactorAuthPort); ok {
- c.AuthPort = port
- } else {
- c.AuthPort = auth_providers.DefaultKeyfactorAuthPort
- }
- }
-
- caErr := c.updateCACerts()
- if caErr != nil {
- return caErr
- }
- return nil
-}
-
-func (c *CommandAuthConfigKeyCloak) updateCACerts() error {
- // check if CommandCACert is set
- if c.AuthCACert == "" {
- // check environment for auth CA cert
- if authCACert, ok := os.LookupEnv(EnvAuthCACert); ok {
- c.AuthCACert = authCACert
- } else {
- return nil
- }
- }
-
- // Load the system certs
- rootCAs, pErr := x509.SystemCertPool()
- if pErr != nil {
- return pErr
- }
- if rootCAs == nil {
- rootCAs = x509.NewCertPool()
- }
-
- // check if CommandCACert is a file
- if _, err := os.Stat(c.AuthCACert); err == nil {
- cert, ioErr := os.ReadFile(c.AuthCACert)
- if ioErr != nil {
- return ioErr
- }
- // Append your custom cert to the pool
- if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
- return fmt.Errorf("failed to append custom CA cert to pool")
- }
- } else {
- // Append your custom cert to the pool
- if ok := rootCAs.AppendCertsFromPEM([]byte(c.AuthCACert)); !ok {
- return fmt.Errorf("failed to append custom CA cert to pool")
- }
- }
-
- // check if client already has a tls config
- if c.HttpClient.Transport == nil {
- // Trust the augmented cert pool in our client
- c.HttpClient.Transport = &http.Transport{
- TLSClientConfig: &tls.Config{
- RootCAs: rootCAs,
- },
- }
- } else {
- // Trust the augmented cert pool in our client
- c.HttpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
- RootCAs: rootCAs,
- }
- }
-
- return nil
-}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials.go b/auth_providers/keycloak/keycloak_auth_client_credentials.go
deleted file mode 100644
index b5d42ed..0000000
--- a/auth_providers/keycloak/keycloak_auth_client_credentials.go
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright 2024 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 keycloak
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "os"
- "strings"
- "time"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
-)
-
-const (
- DefaultKeyfactorAuthRealm = "Keyfactor"
- EnvKeyfactorAuthRealm = "KEYFACTOR_AUTH_REALM"
-)
-
-// CommandAuthKeyCloakClientCredentials represents the configuration needed for Keycloak authentication using client credentials.
-// It embeds CommandAuthConfigKeyCloak and adds additional fields specific to Keycloak client credentials authentication.
-type CommandAuthKeyCloakClientCredentials struct {
- // CommandAuthConfigKeyCloak is a reference to the base configuration needed for authentication to Keyfactor Command API
- CommandAuthConfigKeyCloak
-
- // ClientID is the client ID for Keycloak authentication
- ClientID string `json:"client_id;omitempty"`
-
- // ClientSecret is the client secret for Keycloak authentication
- ClientSecret string `json:"client_secret;omitempty"`
-
- // AccessToken is the access token for Keycloak authentication
- AccessToken string `json:"access_token;omitempty"`
-
- // RefreshToken is the refresh token for Keycloak authentication
- RefreshToken string `json:"refresh_token;omitempty"`
-
- // Expiry is the expiry time of the access token
- Expiry time.Time `json:"expiry;omitempty"`
-
- // Realm is the realm for Keycloak authentication
- Realm string `json:"realm;omitempty"`
-
- // TokenURL is the token URL for Keycloak authentication
- TokenURL string `json:"token_url"`
-}
-
-// Authenticate performs the authentication process for Keycloak using client credentials.
-// It validates the authentication configuration, gets the token, and calls the base authentication method.
-func (c *CommandAuthKeyCloakClientCredentials) Authenticate() error {
- c.AuthType = "client_credentials"
- cErr := c.ValidateAuthConfig()
- if cErr != nil {
- return cErr
- }
-
- //token, tErr := c.GetToken()
- //if tErr != nil {
- // return tErr
- //}
- //if token == "" {
- // return fmt.Errorf("failed to get Bearer token using client credentials")
- //}
- //
- //c.AuthHeader = fmt.Sprintf("Bearer %s", token)
-
- // create oauth client
- oauthy, err := auth_providers.NewOAuthAuthenticatorBuilder().
- WithClientId(c.ClientID).
- WithClientSecret(c.ClientSecret).
- WithTokenUrl(c.TokenURL).
- Build()
-
- if err != nil {
- return err
- }
-
- if oauthy != nil {
- oClient, oerr := oauthy.GetHttpClient()
- if oerr != nil {
- return oerr
- }
- c.SetClient(oClient)
- }
-
- aErr := c.CommandAuthConfig.Authenticate()
- if aErr != nil {
- return aErr
- }
-
- return nil
-}
-
-// setClientId sets the client ID for Keycloak authentication.
-// It retrieves the client ID from environment variables if it's not set.
-func (c *CommandAuthKeyCloakClientCredentials) setClientId() error {
- if c.ClientID == "" {
- if clientID, ok := os.LookupEnv(auth_providers.EnvKeyfactorClientID); ok {
- c.ClientID = clientID
- } else {
- return fmt.Errorf("client_id or environment variable %s is required", auth_providers.EnvKeyfactorClientID)
- }
- }
- return nil
-}
-
-// setClientSecret sets the client secret for Keycloak authentication.
-// It retrieves the client secret from environment variables if it's not set.
-func (c *CommandAuthKeyCloakClientCredentials) setClientSecret() error {
- if c.ClientSecret == "" {
- if clientSecret, ok := os.LookupEnv(auth_providers.EnvKeyfactorClientSecret); ok {
- c.ClientSecret = clientSecret
- } else {
- return fmt.Errorf(
- "client_secret or environment variable %s is required",
- auth_providers.EnvKeyfactorClientSecret,
- )
- }
- }
- return nil
-}
-
-// setRealm sets the realm for Keycloak authentication.
-// It retrieves the realm from environment variables if it's not set.
-func (c *CommandAuthKeyCloakClientCredentials) setRealm() error {
- if c.Realm == "" {
- if realm, ok := os.LookupEnv(EnvKeyfactorAuthRealm); ok {
- c.Realm = realm
- } else {
- c.Realm = DefaultKeyfactorAuthRealm
- }
- }
- return nil
-}
-
-// setTokenURL sets the token URL for Keycloak authentication.
-// It generates the token URL if it's not set.
-func (c *CommandAuthKeyCloakClientCredentials) setTokenURL() error {
- if c.TokenURL == "" {
- if tokenURL, ok := os.LookupEnv(auth_providers.EnvKeyfactorAuthTokenURL); ok {
- c.TokenURL = tokenURL
- } else {
- c.TokenURL = fmt.Sprintf(
- "https://%s:%s/realms/%s/protocol/openid-connect/token",
- c.AuthHostName,
- c.AuthPort,
- c.Realm,
- )
- }
- }
- return nil
-}
-
-// ValidateAuthConfig validates the authentication configuration for Keycloak using client credentials.
-// It checks the client ID, client secret, realm, and token URL, and retrieves them from environment variables if they're not set.
-func (c *CommandAuthKeyCloakClientCredentials) ValidateAuthConfig() error {
- cErr := c.CommandAuthConfigKeyCloak.ValidateAuthConfig()
- if cErr != nil {
- return cErr
- }
-
- cIdErr := c.setClientId()
- if cIdErr != nil {
- return cIdErr
- }
-
- cSecretErr := c.setClientSecret()
- if cSecretErr != nil {
- return cSecretErr
- }
-
- rErr := c.setRealm()
- if rErr != nil {
- return rErr
- }
-
- tErr := c.setTokenURL()
- if tErr != nil {
- return tErr
-
- }
-
- return c.CommandAuthConfig.ValidateAuthConfig()
-}
-
-// GetToken gets the access token for Keycloak authentication.
-// It uses the refresh token if available and not expired, otherwise, it requests a new access token.
-func (c *CommandAuthKeyCloakClientCredentials) GetToken() (string, error) {
- // Check if access token is set in environment variable
- if c.AccessToken == "" {
- if accessToken, ok := os.LookupEnv(auth_providers.EnvKeyfactorAccessToken); ok {
- c.AccessToken = accessToken
-
- // Don't try to refresh as we don't have a refresh token
- return c.AccessToken, nil
- }
- }
-
- if c.AccessToken != "" && time.Now().Before(c.Expiry) {
- return c.AccessToken, nil
- }
-
- // Use refresh token if available and not expired
- if c.RefreshToken != "" && time.Now().After(c.Expiry) {
- return c.refreshAccessToken()
- }
-
- // Otherwise, get a new access token using client credentials
- return c.requestNewToken()
-}
-
-// requestNewToken requests a new access token for Keycloak authentication using client credentials.
-func (c *CommandAuthKeyCloakClientCredentials) requestNewToken() (string, error) {
- formData := url.Values{}
- formData.Set("grant_type", "client_credentials")
- formData.Set("client_id", c.ClientID)
- formData.Set("client_secret", c.ClientSecret)
-
- return c.doTokenRequest(formData.Encode())
-}
-
-// refreshAccessToken refreshes the access token for Keycloak authentication.
-func (c *CommandAuthKeyCloakClientCredentials) refreshAccessToken() (string, error) {
- formData := url.Values{}
- formData.Set("grant_type", "refresh_token")
- formData.Set("client_id", c.ClientID)
- formData.Set("client_secret", c.ClientSecret)
- formData.Set("refresh_token", c.RefreshToken)
- return c.doTokenRequest(formData.Encode())
-}
-
-// doTokenRequest sends a token request to Keycloak and handles the response.
-func (c *CommandAuthKeyCloakClientCredentials) doTokenRequest(data string) (string, error) {
- requestBody := strings.NewReader(data)
- req, reqErr := http.NewRequest("POST", c.TokenURL, requestBody)
- if reqErr != nil {
- return "", reqErr
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-
- resp, tkRespErr := c.HttpClient.Do(req)
- if tkRespErr != nil {
- return "", tkRespErr
- }
- defer resp.Body.Close()
-
- // check response status code
- if resp.StatusCode != http.StatusOK {
- return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
- }
-
- body, ioErr := io.ReadAll(resp.Body)
- if ioErr != nil {
- return "", ioErr
- }
-
- var tokenResponse struct {
- AccessToken string `json:"access_token"`
- RefreshToken string `json:"refresh_token"`
- ExpiresIn int `json:"expires_in"`
- }
- if err := json.Unmarshal(body, &tokenResponse); err != nil {
- return "", err
- }
-
- c.AccessToken = tokenResponse.AccessToken
- c.RefreshToken = tokenResponse.RefreshToken
- c.Expiry = time.Now().Add(time.Duration(tokenResponse.ExpiresIn-30) * time.Second) // Subtract 30 seconds to account for delay
-
- return c.AccessToken, nil
-}
diff --git a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go b/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
deleted file mode 100644
index 64ed06f..0000000
--- a/auth_providers/keycloak/keycloak_auth_client_credentials_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2024 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 keycloak
-
-import (
- "os"
- "testing"
-)
-
-const (
- TestEnvIsClientAuth = "TEST_KEYFACTOR_KC_AUTH"
-)
-
-func TestCommandAuthKeyCloakClientCredentials_AuthEnvironment(t *testing.T) {
-
- if !checkAuthEnvClientCreds() {
- msg := "Skipping test because Keyfactor Command environment is not authenticated with client credentials"
- t.Log(msg)
- t.Skip(msg)
- return
- }
-
- // Create a new CommandAuthKeyCloakClientCredentials instance
- c := &CommandAuthKeyCloakClientCredentials{} //Used environment configuration
-
- // Call the Authenticate method
- err := c.Authenticate()
- if err != nil {
- t.Errorf("Authenticate() error = %v", err)
- return
- }
-
- // Check that the AuthHeader was set correctly
- //expectedAuthHeader := fmt.Sprintf("Bearer %s", c.AccessToken)
- //if c.AuthHeader != expectedAuthHeader {
- // t.Errorf("Authenticate() AuthHeader = %v, want %v", c.AuthHeader, expectedAuthHeader)
- //} else if c.HttpClient.Tra
-}
-
-func checkAuthEnvClientCreds() bool {
- if isKCAuth, ok := os.LookupEnv(TestEnvIsClientAuth); ok {
- if isKCAuth == "true" || isKCAuth == "1" {
- return true
- }
- }
- return false
-}
diff --git a/go.mod b/go.mod
index c1abd39..3c45212 100644
--- a/go.mod
+++ b/go.mod
@@ -16,4 +16,7 @@ module github.com/Keyfactor/keyfactor-auth-client-go
go 1.22
-require golang.org/x/oauth2 v0.22.0
+require (
+ golang.org/x/oauth2 v0.22.0
+ gopkg.in/yaml.v2 v2.4.0
+)
diff --git a/go.sum b/go.sum
index 280e973..17be337 100644
--- a/go.sum
+++ b/go.sum
@@ -2,3 +2,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/lib/main.go b/lib/main.go
new file mode 100644
index 0000000..c1f2736
--- /dev/null
+++ b/lib/main.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "fmt"
+ "math/big"
+ "os"
+ "time"
+)
+
+func main() {
+ // Generate CA private key
+ caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ fmt.Printf("Failed to generate CA private key: %v\n", err)
+ return
+ }
+
+ // Create CA certificate template
+ caTemplate := &x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{
+ Organization: []string{"My CA Organization"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(10, 0, 0), // 10 years
+ KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+ BasicConstraintsValid: true,
+ IsCA: true,
+ }
+
+ // Create CA certificate
+ caCertBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caPrivKey.PublicKey, caPrivKey)
+ if err != nil {
+ fmt.Printf("Failed to create CA certificate: %v\n", err)
+ return
+ }
+
+ // Encode CA certificate to PEM format
+ caCertPEM := pem.EncodeToMemory(
+ &pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: caCertBytes,
+ },
+ )
+
+ // Encode CA private key to PEM format
+ caPrivKeyPEM := pem.EncodeToMemory(
+ &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
+ },
+ )
+
+ // Save CA certificate and private key to files
+ err = os.WriteFile("ca_cert.pem", caCertPEM, 0644)
+ if err != nil {
+ fmt.Printf("Failed to write CA certificate to file: %v\n", err)
+ return
+ }
+ err = os.WriteFile("ca_key.pem", caPrivKeyPEM, 0600)
+ if err != nil {
+ fmt.Printf("Failed to write CA private key to file: %v\n", err)
+ return
+ }
+
+ // Generate leaf private key
+ leafPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ fmt.Printf("Failed to generate leaf private key: %v\n", err)
+ return
+ }
+
+ // Create leaf certificate template
+ leafTemplate := &x509.Certificate{
+ SerialNumber: big.NewInt(2),
+ Subject: pkix.Name{
+ Organization: []string{"My Leaf Organization"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(1, 0, 0), // 1 year
+ KeyUsage: x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
+ }
+
+ // Create leaf certificate signed by the CA
+ leafCertBytes, err := x509.CreateCertificate(
+ rand.Reader,
+ leafTemplate,
+ caTemplate,
+ &leafPrivKey.PublicKey,
+ caPrivKey,
+ )
+ if err != nil {
+ fmt.Printf("Failed to create leaf certificate: %v\n", err)
+ return
+ }
+
+ // Encode leaf certificate to PEM format
+ leafCertPEM := pem.EncodeToMemory(
+ &pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: leafCertBytes,
+ },
+ )
+
+ // Encode leaf private key to PEM format
+ leafPrivKeyPEM := pem.EncodeToMemory(
+ &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: x509.MarshalPKCS1PrivateKey(leafPrivKey),
+ },
+ )
+
+ // Save leaf certificate and private key to files
+ err = os.WriteFile("leaf_cert.pem", leafCertPEM, 0644)
+ if err != nil {
+ fmt.Printf("Failed to write leaf certificate to file: %v\n", err)
+ return
+ }
+ err = os.WriteFile("leaf_key.pem", leafPrivKeyPEM, 0600)
+ if err != nil {
+ fmt.Printf("Failed to write leaf private key to file: %v\n", err)
+ return
+ }
+
+ fmt.Println("CA and leaf certificates generated successfully.")
+}
diff --git a/lib/test_ca_cert.pem b/lib/test_ca_cert.pem
new file mode 100644
index 0000000..3dd6c24
--- /dev/null
+++ b/lib/test_ca_cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9zCCAd+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQKExJNeSBD
+QSBPcmdhbml6YXRpb24wHhcNMjQxMDE2MjIwMTQyWhcNMzQxMDE2MjIwMTQyWjAd
+MRswGQYDVQQKExJNeSBDQSBPcmdhbml6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC3DFiSb9+ykr8kSwlahq3lauN5BhFMsFP4nnYH9M2M0z2c
+cSLwF/ZpGYkzOVbHiwlpuPfmpm0weCnhxfHpX7Yd20g3Jr2M++enYRX8XGprhxw3
+GI1F1UMe/RPJxMhGEMzfIbiWZDXGWJI5v40CItq4Pgvw5jN9NYQlZjQ5YP5wEGI1
+JbYn47pxnL+SctmPIy5647WnNR4EL9Sb5ErNr91hebP/b8LwFhd/f1z+eUo52Xqa
+U/SZYMVrRiPq2nK++kDDoj0SV83q7WscTqRO2qwxyOwklzrUvFn7W1IzijjRciWA
+12GGP1mIaZbPOTIt8dYre9TgVZ1vl9ayrJADGBuNAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR7esJSE+f03gVNuqWa
+fIWHu3CPtTANBgkqhkiG9w0BAQsFAAOCAQEAajiD2zpM77agcz6xqO1+Y2RGb147
+bJb8CPBvZECYAel++dyNGy8kJF4vDiuQ4oRExbM6VUbEiYK2aXeapE2IMVpUY6No
+9JRgmQUDut667njWBv3DhjpV5ZhyI4YHZykXzXjI0eIEbdihfdwx237tvJHOzEeg
+4/xRfDuJxBtZOtctRuMBzkuTCQ85lyrOLLOO81mqmnEbF9d+9WmwS4mu9fta7b5H
+dCqTmtVncnxlE5PmstyeJaVhXbx4MA2BMIhJiusBw5WqKFYFbmhuGpeyEJx3JIZU
+tfE5aamMIbwnwk2VBwfUqHAuQVUxlz/tmRyZ7ALvykZJdJVrUxkKttgjWw==
+-----END CERTIFICATE-----
diff --git a/lib/test_chain.pem b/lib/test_chain.pem
new file mode 100644
index 0000000..5546fd8
--- /dev/null
+++ b/lib/test_chain.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAdCgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQKExJNeSBD
+QSBPcmdhbml6YXRpb24wHhcNMjQxMDE2MjIwMTQyWhcNMjUxMDE2MjIwMTQyWjAf
+MR0wGwYDVQQKExRNeSBMZWFmIE9yZ2FuaXphdGlvbjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAOuOsSztuF7JDXrqvrM6bKSQ6VqpCEHtR5sJnndlrs/V
+Rxsxltk2qBigGIDqQYByM1pcT3WkA2619BHHxaDfcD2Zvx718dUoF1EGzadiEXOR
+946MZlW194qO7++Y8soB4ru36fSGfsK9wr6gOVSYmHINORV+giUvxgbvIpNfQAZI
+39nWNKkR14BLjmkZO6CXomu2W/ZT1WXJ7FDNyF28ww6h5AOCvox9YIRf3OV0GaNE
+kAdoJsw95t64C407P96a6DnaRjWgGkNYTfKf3yPsavilxguXXpqLFlYU7flONA0T
+/ThTjuiRwOShGHEaRr/4iswuxvPWnHTAGXX2K/2wjqkCAwEAAaMxMC8wDgYDVR0P
+AQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG
+9w0BAQsFAAOCAQEAIJje0ts+350wREkB3E0ud/KRL7Ra734YhSxlo+i0UKwGR4oM
+hU2xg+0A7B9uJii9L58oMWTZ+JJpA1Fb4tagksUuBI/rxjNWQ7QSvlhx2Zkwsr5x
+l0nlfxMuzfgRBD/eafxIaD/Li/iiPHQNJyU4iNImmw2r8IW+rtbU/sCVG/OjqZEc
+oKsw+qk9veyH/oZb19HSpK28A6voc2YuNsL4ghMbMWicITFle+Sv6yBEHDIxvQB6
+2pvsQ+H6EY25YfAz2AQNaz6U32CA6KdaQQh87wXAnagM4S6s7SmsZVgX/vGcUS5n
+91NEq4QRzFuINWPF38AsyOR9r2B2xuuCrulsQQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC9zCCAd+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQKExJNeSBD
+QSBPcmdhbml6YXRpb24wHhcNMjQxMDE2MjIwMTQyWhcNMzQxMDE2MjIwMTQyWjAd
+MRswGQYDVQQKExJNeSBDQSBPcmdhbml6YXRpb24wggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC3DFiSb9+ykr8kSwlahq3lauN5BhFMsFP4nnYH9M2M0z2c
+cSLwF/ZpGYkzOVbHiwlpuPfmpm0weCnhxfHpX7Yd20g3Jr2M++enYRX8XGprhxw3
+GI1F1UMe/RPJxMhGEMzfIbiWZDXGWJI5v40CItq4Pgvw5jN9NYQlZjQ5YP5wEGI1
+JbYn47pxnL+SctmPIy5647WnNR4EL9Sb5ErNr91hebP/b8LwFhd/f1z+eUo52Xqa
+U/SZYMVrRiPq2nK++kDDoj0SV83q7WscTqRO2qwxyOwklzrUvFn7W1IzijjRciWA
+12GGP1mIaZbPOTIt8dYre9TgVZ1vl9ayrJADGBuNAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR7esJSE+f03gVNuqWa
+fIWHu3CPtTANBgkqhkiG9w0BAQsFAAOCAQEAajiD2zpM77agcz6xqO1+Y2RGb147
+bJb8CPBvZECYAel++dyNGy8kJF4vDiuQ4oRExbM6VUbEiYK2aXeapE2IMVpUY6No
+9JRgmQUDut667njWBv3DhjpV5ZhyI4YHZykXzXjI0eIEbdihfdwx237tvJHOzEeg
+4/xRfDuJxBtZOtctRuMBzkuTCQ85lyrOLLOO81mqmnEbF9d+9WmwS4mu9fta7b5H
+dCqTmtVncnxlE5PmstyeJaVhXbx4MA2BMIhJiusBw5WqKFYFbmhuGpeyEJx3JIZU
+tfE5aamMIbwnwk2VBwfUqHAuQVUxlz/tmRyZ7ALvykZJdJVrUxkKttgjWw==
+-----END CERTIFICATE-----
diff --git a/lib/test_leaf_cert.pem b/lib/test_leaf_cert.pem
new file mode 100644
index 0000000..379d028
--- /dev/null
+++ b/lib/test_leaf_cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAdCgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQKExJNeSBD
+QSBPcmdhbml6YXRpb24wHhcNMjQxMDE2MjIwMTQyWhcNMjUxMDE2MjIwMTQyWjAf
+MR0wGwYDVQQKExRNeSBMZWFmIE9yZ2FuaXphdGlvbjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAOuOsSztuF7JDXrqvrM6bKSQ6VqpCEHtR5sJnndlrs/V
+Rxsxltk2qBigGIDqQYByM1pcT3WkA2619BHHxaDfcD2Zvx718dUoF1EGzadiEXOR
+946MZlW194qO7++Y8soB4ru36fSGfsK9wr6gOVSYmHINORV+giUvxgbvIpNfQAZI
+39nWNKkR14BLjmkZO6CXomu2W/ZT1WXJ7FDNyF28ww6h5AOCvox9YIRf3OV0GaNE
+kAdoJsw95t64C407P96a6DnaRjWgGkNYTfKf3yPsavilxguXXpqLFlYU7flONA0T
+/ThTjuiRwOShGHEaRr/4iswuxvPWnHTAGXX2K/2wjqkCAwEAAaMxMC8wDgYDVR0P
+AQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG
+9w0BAQsFAAOCAQEAIJje0ts+350wREkB3E0ud/KRL7Ra734YhSxlo+i0UKwGR4oM
+hU2xg+0A7B9uJii9L58oMWTZ+JJpA1Fb4tagksUuBI/rxjNWQ7QSvlhx2Zkwsr5x
+l0nlfxMuzfgRBD/eafxIaD/Li/iiPHQNJyU4iNImmw2r8IW+rtbU/sCVG/OjqZEc
+oKsw+qk9veyH/oZb19HSpK28A6voc2YuNsL4ghMbMWicITFle+Sv6yBEHDIxvQB6
+2pvsQ+H6EY25YfAz2AQNaz6U32CA6KdaQQh87wXAnagM4S6s7SmsZVgX/vGcUS5n
+91NEq4QRzFuINWPF38AsyOR9r2B2xuuCrulsQQ==
+-----END CERTIFICATE-----
diff --git a/tag.sh b/tag.sh
index 1f84e80..f1086ab 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.6
+RC_VERSION=rc.8
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From adbb9b8d426bfcc03b9773962f4861cd5f83b444 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 16 Oct 2024 17:11:23 -0700
Subject: [PATCH 28/57] fix(tests): update test environments
---
.github/config/environments.tf | 27 +++++++++++++++++++--------
.github/config/variables.tf | 10 +++++-----
.github/workflows/go_tests.yml | 6 ++++--
3 files changed, 28 insertions(+), 15 deletions(-)
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
index 7719571..0b42a3f 100644
--- a/.github/config/environments.tf
+++ b/.github/config/environments.tf
@@ -8,15 +8,26 @@ module "keyfactor_github_test_environment_ad_10_5_0" {
keyfactor_password = var.keyfactor_password_10_5_0
}
-module "keyfactor_github_test_environment_11_5_0_kc" {
- source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
+# module "keyfactor_github_test_environment_11_5_0_kc" {
+# source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
+#
+# gh_environment_name = "KFC_11_5_0_KC"
+# gh_repo_name = data.github_repository.repo.name
+# keyfactor_hostname = var.keyfactor_hostname_11_5_0_KC
+# keyfactor_client_id = var.keyfactor_client_id_11_5_0
+# keyfactor_client_secret = var.keyfactor_client_secret_11_5_0
+# keyfactor_auth_hostname = var.keyfactor_auth_hostname_11_5_0_KC
+# keyfactor_tls_skip_verify = true
+# }
- gh_environment_name = "KFC_11_5_0_KC"
+module "keyfactor_github_test_environment_12_3_0_kc" {
+ source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git?ref=main"
+
+ gh_environment_name = "KFC_12_3_0_KC"
gh_repo_name = data.github_repository.repo.name
- keyfactor_hostname = var.keyfactor_hostname_11_5_0_KC
- keyfactor_client_id = var.keyfactor_client_id_11_5_0
- keyfactor_client_secret = var.keyfactor_client_secret_11_5_0
- keyfactor_auth_hostname = var.keyfactor_auth_hostname_11_5_0_KC
+ keyfactor_hostname = var.keyfactor_hostname_12_3_0_KC
+ keyfactor_auth_token_url = var.keyfactor_auth_token_url_12_3_0_KC
+ keyfactor_client_id = var.keyfactor_client_id_12_3_0
+ keyfactor_client_secret = var.keyfactor_client_secret_12_3_0
keyfactor_tls_skip_verify = true
}
-
diff --git a/.github/config/variables.tf b/.github/config/variables.tf
index c61463b..5353f86 100644
--- a/.github/config/variables.tf
+++ b/.github/config/variables.tf
@@ -14,25 +14,25 @@ variable "keyfactor_password_10_5_0" {
type = string
}
-variable "keyfactor_client_id_11_5_0" {
+variable "keyfactor_client_id_12_3_0" {
description = "The client ID to authenticate with the Keyfactor instance using Keycloak client credentials"
type = string
}
-variable "keyfactor_client_secret_11_5_0" {
+variable "keyfactor_client_secret_12_3_0" {
description = "The client secret to authenticate with the Keyfactor instance using Keycloak client credentials"
type = string
}
-variable "keyfactor_hostname_11_5_0_KC" {
+variable "keyfactor_hostname_12_3_0_KC" {
description = "The hostname of the Keyfactor instance"
type = string
default = "int-oidc-lab.eastus2.cloudapp.azure.com"
}
-variable "keyfactor_auth_hostname_11_5_0_KC" {
+variable "keyfactor_auth_token_url_12_3_0_KC" {
description = "The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token"
type = string
- default = "int-oidc-lab.eastus2.cloudapp.azure.com"
+ default = "https://int-oidc-lab.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"
}
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index cc67909..1d9096d 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -1,6 +1,8 @@
name: Go Test Workflow
-on: [ push ]
+on:
+ push:
+ workflow_dispatch:
jobs:
test:
@@ -8,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- environment: [ "KFC_10_5_0", "KFC_11_5_0_KC"]
+ environment: [ "KFC_10_5_0", "KFC_12_3_0_KC"]
environment: ${{ matrix.environment }}
steps:
- name: Check out code
From 6f0d88467c040280f6c34e763f1a9b1a87d99482 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 16 Oct 2024 17:16:06 -0700
Subject: [PATCH 29/57] fix(tests): Only run basic auth tests against basic
auth environment and oauth tests against an oauth environment
---
auth_providers/auth_basic_test.go | 28 ++++++++++++++++++++++++++++
auth_providers/auth_oauth_test.go | 25 +++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 50dcff9..d95f0d9 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -9,6 +9,12 @@ import (
)
func TestBasicAuthAuthenticator_GetHttpClient(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_KC_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "true" {
+ t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
+ return
+ }
+
auth := &auth_providers.BasicAuthAuthenticator{
Client: &http.Client{},
}
@@ -24,6 +30,11 @@ func TestBasicAuthAuthenticator_GetHttpClient(t *testing.T) {
}
func TestCommandAuthConfigBasic_ValidateAuthConfig(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_KC_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "true" {
+ t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandAuthConfigBasic{
Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
@@ -36,6 +47,12 @@ func TestCommandAuthConfigBasic_ValidateAuthConfig(t *testing.T) {
}
func TestCommandAuthConfigBasic_GetHttpClient(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_KC_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "true" {
+ t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
+ return
+ }
+
config := &auth_providers.CommandAuthConfigBasic{
Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
@@ -52,6 +69,12 @@ func TestCommandAuthConfigBasic_GetHttpClient(t *testing.T) {
}
func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_KC_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "true" {
+ t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
+ return
+ }
+
config := &auth_providers.CommandAuthConfigBasic{}
err := config.Authenticate()
@@ -61,6 +84,11 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
}
func TestCommandAuthConfigBasic_Build(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_KC_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_KC_AUTH") == "true" {
+ t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandAuthConfigBasic{
Username: os.Getenv(auth_providers.EnvKeyfactorUsername),
Password: os.Getenv(auth_providers.EnvKeyfactorPassword),
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 5b17147..82a79a8 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -9,6 +9,11 @@ import (
)
func TestOAuthAuthenticator_GetHttpClient(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_AD_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "true" {
+ t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
+ return
+ }
auth := &auth_providers.OAuthAuthenticator{
Client: &http.Client{},
}
@@ -24,6 +29,11 @@ func TestOAuthAuthenticator_GetHttpClient(t *testing.T) {
}
func TestCommandConfigOauth_ValidateAuthConfig(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_AD_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "true" {
+ t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandConfigOauth{
ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
@@ -37,6 +47,11 @@ func TestCommandConfigOauth_ValidateAuthConfig(t *testing.T) {
}
func TestCommandConfigOauth_GetHttpClient(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_AD_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "true" {
+ t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandConfigOauth{
ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
@@ -55,6 +70,11 @@ func TestCommandConfigOauth_GetHttpClient(t *testing.T) {
}
func TestCommandConfigOauth_Authenticate(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_AD_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "true" {
+ t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandConfigOauth{
ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
@@ -69,6 +89,11 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
}
func TestCommandConfigOauth_Build(t *testing.T) {
+ // Skip test if TEST_KEYFACTOR_AD_AUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "1" || os.Getenv("TEST_KEYFACTOR_AD_AUTH") == "true" {
+ t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
+ return
+ }
config := &auth_providers.CommandConfigOauth{
ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
From a52adff16a02a62c47d47729289b864d80f9027c Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 17 Oct 2024 10:11:48 -0700
Subject: [PATCH 30/57] fix(config): Remove `AuthPort` and rename
`AuthHostname` to `OAuthTokenUrl` fix(config):`AuthProvider.Parameters` is
now `map[string]interface` vs `interface` chore(docs): Add docs around GitHub
test environments chore(docs): Move auth config examples and schema to `lib`
---
.github/config/MODULE.MD | 41 +++
.github/config/Makefile | 26 ++
.github/config/README.md | 92 +++++
README.md | 30 +-
auth_config/command_config.go | 16 +-
auth_config/command_config_test.go | 345 ++++++++++++++++++
.../config}/auth_config_schema.json | 0
lib/config/basic_auth_config_example.json | 18 +
.../config/full_auth_config_example.json | 10 +-
lib/config/oauth_config_example.json | 18 +
10 files changed, 563 insertions(+), 33 deletions(-)
create mode 100644 .github/config/MODULE.MD
create mode 100644 .github/config/Makefile
create mode 100644 .github/config/README.md
create mode 100644 auth_config/command_config_test.go
rename {auth_config => lib/config}/auth_config_schema.json (100%)
create mode 100644 lib/config/basic_auth_config_example.json
rename auth_config/auth_config_example.json => lib/config/full_auth_config_example.json (80%)
create mode 100644 lib/config/oauth_config_example.json
diff --git a/.github/config/MODULE.MD b/.github/config/MODULE.MD
new file mode 100644
index 0000000..4a0f4ab
--- /dev/null
+++ b/.github/config/MODULE.MD
@@ -0,0 +1,41 @@
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [github](#requirement\_github) | >=6.2 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [github](#provider\_github) | 6.3.1 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [keyfactor\_github\_test\_environment\_12\_3\_0\_kc](#module\_keyfactor\_github\_test\_environment\_12\_3\_0\_kc) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
+| [keyfactor\_github\_test\_environment\_ad\_10\_5\_0](#module\_keyfactor\_github\_test\_environment\_ad\_10\_5\_0) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [github_repository.repo](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/repository) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [keyfactor\_auth\_token\_url\_12\_3\_0\_KC](#input\_keyfactor\_auth\_token\_url\_12\_3\_0\_KC) | The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token | `string` | `"https://int-oidc-lab.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"` | no |
+| [keyfactor\_client\_id\_12\_3\_0](#input\_keyfactor\_client\_id\_12\_3\_0) | The client ID to authenticate with the Keyfactor instance using Keycloak client credentials | `string` | n/a | yes |
+| [keyfactor\_client\_secret\_12\_3\_0](#input\_keyfactor\_client\_secret\_12\_3\_0) | The client secret to authenticate with the Keyfactor instance using Keycloak client credentials | `string` | n/a | yes |
+| [keyfactor\_hostname\_10\_5\_0](#input\_keyfactor\_hostname\_10\_5\_0) | The hostname of the Keyfactor instance | `string` | `"integrations1050-lab.kfdelivery.com"` | no |
+| [keyfactor\_hostname\_12\_3\_0\_KC](#input\_keyfactor\_hostname\_12\_3\_0\_KC) | The hostname of the Keyfactor instance | `string` | `"int-oidc-lab.eastus2.cloudapp.azure.com"` | no |
+| [keyfactor\_password\_10\_5\_0](#input\_keyfactor\_password\_10\_5\_0) | The password to authenticate with the Keyfactor instance | `string` | n/a | yes |
+| [keyfactor\_username\_10\_5\_0](#input\_keyfactor\_username\_10\_5\_0) | The username to authenticate with the Keyfactor instance | `string` | n/a | yes |
+
+## Outputs
+
+No outputs.
diff --git a/.github/config/Makefile b/.github/config/Makefile
new file mode 100644
index 0000000..f67d9df
--- /dev/null
+++ b/.github/config/Makefile
@@ -0,0 +1,26 @@
+.DEFAULT_GOAL := help
+
+##@ Utility
+help: ## Display this help
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+
+deps: ## Install deps for macos
+ @brew install pre-commit tflint terraform terraform-docs
+
+docs: ## Run terraform-docs to update module docs.
+ @terraform-docs markdown . > MODULE.MD
+ @terraform-docs markdown table --output-file README.md --output-mode inject .
+
+lint: ## Run tflint
+ @tflint
+
+validate: ## Run terraform validate
+ @terraform init --upgrade
+ @terraform validate
+
+precommit/add: ## Install pre-commit hook
+ @pre-commit install
+
+precommit/remove: ## Uninstall pre-commit hook
+ @pre-commit uninstall
+
diff --git a/.github/config/README.md b/.github/config/README.md
new file mode 100644
index 0000000..e1a8977
--- /dev/null
+++ b/.github/config/README.md
@@ -0,0 +1,92 @@
+# GitHub Test Environment Setup
+
+This code sets up GitHub environments for testing against Keyfactor Command instances that are configured to use
+Active Directory or Keycloak for authentication.
+
+## Requirements
+
+1. Terraform >= 1.0
+2. GitHub Provider >= 6.2
+3. Keyfactor Command instance(s) configured to use Active Directory or Keycloak for authentication
+4. AD or Keycloak credentials for authenticating to the Keyfactor Command instance(s)
+5. A GitHub token with access and permissions to the repository where the environments will be created
+
+## Adding a new environment
+
+Modify the `environments.tf` file to include the new environment module. The module should be named appropriately.
+Example:
+
+### Active Directory Environment
+
+```hcl
+module "keyfactor_github_test_environment_ad_10_5_0" {
+ source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git?ref=main"
+
+ gh_environment_name = "KFC_10_5_0" # Keyfactor Command 10.5.0 environment using Active Directory(/Basic Auth)
+ gh_repo_name = data.github_repository.repo.name
+ keyfactor_hostname = var.keyfactor_hostname_10_5_0
+ keyfactor_username = var.keyfactor_username_10_5_0
+ keyfactor_password = var.keyfactor_password_10_5_0
+}
+```
+
+### oAuth Client Environment
+
+```hcl
+module "keyfactor_github_test_environment_12_3_0_kc" {
+ source = "git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-kc.git?ref=main"
+
+ gh_environment_name = "KFC_12_3_0_KC" # Keyfactor Command 12.3.0 environment using Keycloak
+ gh_repo_name = data.github_repository.repo.name
+ keyfactor_hostname = var.keyfactor_hostname_12_3_0_KC
+ keyfactor_auth_token_url = var.keyfactor_auth_token_url_12_3_0_KC
+ keyfactor_client_id = var.keyfactor_client_id_12_3_0
+ keyfactor_client_secret = var.keyfactor_client_secret_12_3_0
+ keyfactor_tls_skip_verify = true
+}
+```
+
+
+
+## Requirements
+
+| Name | Version |
+|---------------------------------------------------------------------------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [github](#requirement\_github) | >=6.2 |
+
+## Providers
+
+| Name | Version |
+|------------------------------------------------------------|---------|
+| [github](#provider\_github) | 6.3.1 |
+
+## Modules
+
+| Name | Source | Version |
+|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|---------|
+| [keyfactor\_github\_test\_environment\_12\_3\_0\_kc](#module\_keyfactor\_github\_test\_environment\_12\_3\_0\_kc) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
+| [keyfactor\_github\_test\_environment\_ad\_10\_5\_0](#module\_keyfactor\_github\_test\_environment\_ad\_10\_5\_0) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
+
+## Resources
+
+| Name | Type |
+|---------------------------------------------------------------------------------------------------------------------------|-------------|
+| [github_repository.repo](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/repository) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|--------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------|:--------:|
+| [keyfactor\_auth\_token\_url\_12\_3\_0\_KC](#input\_keyfactor\_auth\_token\_url\_12\_3\_0\_KC) | The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token | `string` | `"https://int-oidc-lab.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"` | no |
+| [keyfactor\_client\_id\_12\_3\_0](#input\_keyfactor\_client\_id\_12\_3\_0) | The client ID to authenticate with the Keyfactor instance using Keycloak client credentials | `string` | n/a | yes |
+| [keyfactor\_client\_secret\_12\_3\_0](#input\_keyfactor\_client\_secret\_12\_3\_0) | The client secret to authenticate with the Keyfactor instance using Keycloak client credentials | `string` | n/a | yes |
+| [keyfactor\_hostname\_10\_5\_0](#input\_keyfactor\_hostname\_10\_5\_0) | The hostname of the Keyfactor instance | `string` | `"integrations1050-lab.kfdelivery.com"` | no |
+| [keyfactor\_hostname\_12\_3\_0\_KC](#input\_keyfactor\_hostname\_12\_3\_0\_KC) | The hostname of the Keyfactor instance | `string` | `"int-oidc-lab.eastus2.cloudapp.azure.com"` | no |
+| [keyfactor\_password\_10\_5\_0](#input\_keyfactor\_password\_10\_5\_0) | The password to authenticate with the Keyfactor instance | `string` | n/a | yes |
+| [keyfactor\_username\_10\_5\_0](#input\_keyfactor\_username\_10\_5\_0) | The username to authenticate with the Keyfactor instance | `string` | n/a | yes |
+
+## Outputs
+
+No outputs.
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 32c4445..50aa115 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,8 @@ Client library for authenticating to Keyfactor Command
- [Environment Variables](#environment-variables)
* [Global](#global)
- * [Active Directory](#active-directory)
- * [Keycloak](#keycloak)
- + [Client Credentials](#client-credentials)
+ * [Basic Auth](#basic-auth)
+ * [oAuth Client Credentials](#oauth-client-credentials)
- [Test Environment Variables](#test-environment-variables)
@@ -25,7 +24,7 @@ Client library for authenticating to Keyfactor Command
| KEYFACTOR_SKIP_VERIFY | Skip TLS verification when connecting to Keyfactor Command | `false` |
| KEYFACTOR_CA_CERT | Either a file path or PEM encoded string to a CA certificate | |
-### Active Directory
+### Basic Auth
| Name | Description | Default |
|--------------------|---------------------------------------------------------------------------------------------|---------|
@@ -33,22 +32,15 @@ Client library for authenticating to Keyfactor Command
| KEYFACTOR_PASSWORD | Password associated with Active Directory username to authenticate to Keyfactor Command API | |
| KEYFACTOR_DOMAIN | Active Directory domain of user. Can be implied from username if it contains `@` or `\\` | |
-### Keycloak
+### oAuth Client Credentials
-| Name | Description | Default |
-|-------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------|
-| KEYFACTOR_AUTH_HOSTNAME | Hostname of Keycloak instance to authenticate to Keyfactor Command | |
-| KEYFACTOR_AUTH_REALM | Keyfactor Auth Realm | `Keyfactor` |
-| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
-| KEYFACTOR_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 | |
-
-#### Client Credentials
-
-| Name | Description | Default |
-|------------------------------|------------------------------|---------|
-| KEYFACTOR_AUTH_CLIENT_ID | Keyfactor Auth Client ID | |
-| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
+| Name | Description | Default |
+|------------------------------|---------------------------------------------------------------------------------------------------------------------------------|----------|
+| KEYFACTOR_AUTH_CLIENT_ID | Keyfactor Auth Client ID | |
+| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
+| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
+| KEYFACTOR_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 | |
## Test Environment Variables
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index 4fb5bd4..c696b13 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -10,15 +10,15 @@ import (
// Server represents the server configuration for authentication.
type Server struct {
- Host string `json:"host,omitempty" yaml:"host,omitempty"` // Host is the Command server DNS name or IP address.
- Port int `json:"port,omitempty" yaml:"port,omitempty"` // Port is the Command server port.
- AuthHostname string `json:"auth_hostname,omitempty" yaml:"auth_hostname,omitempty"` // AuthHostname is the authentication hostname.
- AuthPort int `json:"auth_port,omitempty" yaml:"auth_port,omitempty"` // AuthPort is the authentication port.
+ Host string `json:"host,omitempty" yaml:"host,omitempty"` // Host is the Command server DNS name or IP address.
+ Port int `json:"port,omitempty" yaml:"port,omitempty"` // Port is the Command server port.
+ //AuthPort int `json:"auth_port,omitempty" yaml:"auth_port,omitempty"` // AuthPort is the authentication port.
Username string `json:"username,omitempty" yaml:"username,omitempty"` // Username is the username for authentication.
Password string `json:"password,omitempty" yaml:"password,omitempty"` // Password is the password for authentication.
+ Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` // Domain is the domain for authentication.
ClientID string `json:"client_id,omitempty" yaml:"client_id,omitempty"` // ClientID is the client ID for OAuth.
ClientSecret string `json:"client_secret,omitempty" yaml:"client_secret,omitempty"` // ClientSecret is the client secret for OAuth.
- Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` // Domain is the domain for authentication.
+ OAuthTokenUrl string `json:"oauth_token_url,omitempty" yaml:"auth_hostname,omitempty"` // OAuthTokenUrl is full URL for OAuth token request endpoint.
APIPath string `json:"api_path,omitempty" yaml:"api_path,omitempty"` // APIPath is the API path.
AuthProvider AuthProvider `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` // AuthProvider contains the authentication provider details.
SkipTLSVerify bool `json:"skip_tls_verify,omitempty" yaml:"skip_tls_verify,omitempty"` // TLSVerify determines whether to verify the TLS certificate.
@@ -28,9 +28,9 @@ omitempty"` // CACertPath is the path to the CA certificate to trust.
// AuthProvider represents the authentication provider configuration.
type AuthProvider struct {
- Type string `json:"type,omitempty" yaml:"type,omitempty"` // Type is the type of authentication provider.
- Profile string `json:"profile,omitempty" yaml:"profile,omitempty"` // Profile is the profile of the authentication provider.
- Parameters interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` // Parameters are additional parameters for the authentication provider.
+ Type string `json:"type,omitempty" yaml:"type,omitempty"` // Type is the type of authentication provider.
+ Profile string `json:"profile,omitempty" yaml:"profile,omitempty"` // Profile is the profile of the authentication provider.
+ Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` // Parameters are additional parameters for the authentication provider.
}
// Config represents the overall configuration structure.
diff --git a/auth_config/command_config_test.go b/auth_config/command_config_test.go
new file mode 100644
index 0000000..229799e
--- /dev/null
+++ b/auth_config/command_config_test.go
@@ -0,0 +1,345 @@
+package authconfig_test
+
+import (
+ "encoding/json"
+ "os"
+ "testing"
+
+ "gopkg.in/yaml.v2"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
+)
+
+func TestReadServerFromJSON(t *testing.T) {
+ filePath := "test_config.json"
+ server := &authconfig.Server{
+ Host: "localhost",
+ Port: 8080,
+ OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
+ Username: "user",
+ Password: "pass",
+ ClientID: "client_id",
+ ClientSecret: "client_secret",
+ Domain: "domain",
+ APIPath: "api",
+ }
+
+ err := authconfig.WriteServerToJSON(filePath, server)
+ if err != nil {
+ t.Fatalf("failed to write server to JSON: %v", err)
+ }
+ defer os.Remove(filePath)
+
+ readServer, err := authconfig.ReadServerFromJSON(filePath)
+ if err != nil {
+ t.Fatalf("failed to read server from JSON: %v", err)
+ }
+
+ if !compareServers(readServer, server) {
+ t.Fatalf("expected %v, got %v", server, readServer)
+ }
+}
+
+func TestWriteServerToJSON(t *testing.T) {
+ filePath := "test_server.json"
+ server := &authconfig.Server{
+ Host: "localhost",
+ Port: 8080,
+ OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
+ Username: "user",
+ Password: "pass",
+ ClientID: "client_id",
+ ClientSecret: "client_secret",
+ Domain: "domain",
+ APIPath: "api",
+ }
+
+ err := authconfig.WriteServerToJSON(filePath, server)
+ if err != nil {
+ t.Fatalf("failed to write server to JSON: %v", err)
+ }
+ defer os.Remove(filePath)
+
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+
+ var readServer authconfig.Server
+ err = json.Unmarshal(file, &readServer)
+ if err != nil {
+ t.Fatalf("failed to unmarshal JSON: %v", err)
+ }
+
+ if !compareServers(&readServer, server) {
+ t.Fatalf("expected %v, got %v", server, readServer)
+ }
+}
+
+func TestReadServerFromYAML(t *testing.T) {
+ filePath := "test_server.yaml"
+ server := &authconfig.Server{
+ Host: "localhost",
+ Port: 8080,
+ OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
+ Username: "user",
+ Password: "pass",
+ ClientID: "client_id",
+ ClientSecret: "client_secret",
+ Domain: "domain",
+ APIPath: "api",
+ }
+
+ err := authconfig.WriteServerToYAML(filePath, server)
+ if err != nil {
+ t.Fatalf("failed to write server to YAML: %v", err)
+ }
+ defer os.Remove(filePath)
+
+ readServer, err := authconfig.ReadServerFromYAML(filePath)
+ if err != nil {
+ t.Fatalf("failed to read server from YAML: %v", err)
+ }
+
+ if !compareServers(readServer, server) {
+ t.Fatalf("expected %v, got %v", server, readServer)
+ }
+}
+
+func TestWriteServerToYAML(t *testing.T) {
+ filePath := "test_server.yaml"
+ server := &authconfig.Server{
+ Host: "localhost",
+ Port: 8080,
+ OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
+ Username: "user",
+ Password: "pass",
+ ClientID: "client_id",
+ ClientSecret: "client_secret",
+ Domain: "domain",
+ APIPath: "api",
+ }
+
+ err := authconfig.WriteServerToYAML(filePath, server)
+ if err != nil {
+ t.Fatalf("failed to write server to YAML: %v", err)
+ }
+ defer os.Remove(filePath)
+
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+
+ var readServer authconfig.Server
+ err = yaml.Unmarshal(file, &readServer)
+ if err != nil {
+ t.Fatalf("failed to unmarshal YAML: %v", err)
+ }
+
+ if !compareServers(&readServer, server) {
+ t.Fatalf("expected %v, got %v", server, readServer)
+ }
+}
+
+func TestMergeConfigFromFile(t *testing.T) {
+ filePath := "test_config.json"
+ config := &authconfig.Config{
+ Servers: map[string]authconfig.Server{
+ "server1": {
+ Host: "localhost",
+ Port: 8080,
+ },
+ },
+ }
+
+ err := authconfig.WriteConfigToJSON(filePath, config)
+ if err != nil {
+ t.Fatalf("failed to write config to JSON: %v", err)
+ }
+ defer os.Remove(filePath)
+
+ newConfig := &authconfig.Config{
+ Servers: map[string]authconfig.Server{
+ "server2": {
+ Host: "remotehost",
+ Port: 9090,
+ },
+ },
+ }
+
+ err = authconfig.MergeConfigFromFile(filePath, newConfig)
+ if err != nil {
+ t.Fatalf("failed to merge config from file: %v", err)
+ }
+
+ if len(newConfig.Servers) != 2 {
+ t.Fatalf("expected 2 servers, got %d", len(newConfig.Servers))
+ }
+
+ if newConfig.Servers["server1"].Host != "localhost" {
+ t.Fatalf("expected server1 host to be localhost, got %s", newConfig.Servers["server1"].Host)
+ }
+
+ if newConfig.Servers["server2"].Host != "remotehost" {
+ t.Fatalf("expected server2 host to be remotehost, got %s", newConfig.Servers["server2"].Host)
+ }
+}
+
+func TestReadFullAuthConfigExample(t *testing.T) {
+ filePath := "../lib/config/full_auth_config_example.json"
+ expectedConfig := &authconfig.Config{
+ Servers: map[string]authconfig.Server{
+ "default": {
+ Host: "keyfactor.command.kfdelivery.com",
+ OAuthTokenUrl: "idp.keyfactor.command.kfdelivery.com",
+ Username: "keyfactor",
+ Password: "password",
+ ClientID: "client-id",
+ ClientSecret: "client-secret",
+ Domain: "command",
+ APIPath: "KeyfactorAPI",
+ AuthProvider: authconfig.AuthProvider{
+ Type: "azid",
+ Profile: "azure",
+ Parameters: map[string]interface{}{
+ "secret_name": "command-config-azure",
+ "vault_name": "keyfactor-secrets",
+ },
+ },
+ },
+ "server2": {
+ Host: "keyfactor2.command.kfdelivery.com",
+ OAuthTokenUrl: "idp.keyfactor2.command.kfdelivery.com",
+ Username: "keyfactor2",
+ Password: "password2",
+ ClientID: "client-id2",
+ ClientSecret: "client-secret2",
+ Domain: "command",
+ APIPath: "KeyfactorAPI",
+ AuthProvider: authconfig.AuthProvider{
+ Type: "azid",
+ Profile: "azure",
+ Parameters: map[string]interface{}{
+ "secret_name": "command-config-azure2",
+ "vault_name": "keyfactor-secrets",
+ },
+ },
+ },
+ },
+ }
+
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+
+ var config authconfig.Config
+ err = json.Unmarshal(file, &config)
+ if err != nil {
+ t.Fatalf("failed to unmarshal JSON: %v", err)
+ }
+
+ if !compareConfigs(&config, expectedConfig) {
+ t.Fatalf("expected %v, got %v", expectedConfig, config)
+ }
+}
+
+func TestReadOAuthConfigExample(t *testing.T) {
+ filePath := "../lib/config/oauth_config_example.json"
+ expectedConfig := &authconfig.Config{
+ Servers: map[string]authconfig.Server{
+ "default": {
+ Host: "keyfactor.command.kfdelivery.com",
+ OAuthTokenUrl: "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ ClientID: "client-id",
+ ClientSecret: "client-secret",
+ APIPath: "KeyfactorAPI",
+ },
+ "server2": {
+ Host: "keyfactor.command.kfdelivery.com",
+ OAuthTokenUrl: "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ ClientID: "client-id",
+ ClientSecret: "client-secret",
+ APIPath: "KeyfactorAPI",
+ },
+ },
+ }
+
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+
+ var config authconfig.Config
+ err = json.Unmarshal(file, &config)
+ if err != nil {
+ t.Fatalf("failed to unmarshal JSON: %v", err)
+ }
+
+ if !compareConfigs(&config, expectedConfig) {
+ t.Fatalf("expected %v, got %v", expectedConfig, config)
+ }
+}
+
+func TestReadBasicAuthConfigExample(t *testing.T) {
+ filePath := "../lib/config/basic_auth_config_example.json"
+ expectedConfig := &authconfig.Config{
+ Servers: map[string]authconfig.Server{
+ "default": {
+ Host: "keyfactor.command.kfdelivery.com",
+ Username: "keyfactor",
+ Password: "password",
+ Domain: "command",
+ APIPath: "KeyfactorAPI",
+ },
+ "server2": {
+ Host: "keyfactor2.command.kfdelivery.com",
+ Username: "keyfactor2",
+ Password: "password2",
+ Domain: "command",
+ APIPath: "Keyfactor/API",
+ },
+ },
+ }
+
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Fatalf("failed to read file: %v", err)
+ }
+
+ var config authconfig.Config
+ err = json.Unmarshal(file, &config)
+ if err != nil {
+ t.Fatalf("failed to unmarshal JSON: %v", err)
+ }
+
+ if !compareConfigs(&config, expectedConfig) {
+ t.Fatalf("expected %v, got %v", expectedConfig, config)
+ }
+}
+
+func compareConfigs(a, b *authconfig.Config) bool {
+ if len(a.Servers) != len(b.Servers) {
+ return false
+ }
+ for key, serverA := range a.Servers {
+ serverB, exists := b.Servers[key]
+ if !exists || !compareServers(&serverA, &serverB) {
+ return false
+ }
+ }
+ return true
+}
+
+func compareServers(a, b *authconfig.Server) bool {
+ return a.Host == b.Host &&
+ a.Port == b.Port &&
+ a.OAuthTokenUrl == b.OAuthTokenUrl &&
+ a.Username == b.Username &&
+ a.Password == b.Password &&
+ a.ClientID == b.ClientID &&
+ a.ClientSecret == b.ClientSecret &&
+ a.Domain == b.Domain &&
+ a.APIPath == b.APIPath
+}
diff --git a/auth_config/auth_config_schema.json b/lib/config/auth_config_schema.json
similarity index 100%
rename from auth_config/auth_config_schema.json
rename to lib/config/auth_config_schema.json
diff --git a/lib/config/basic_auth_config_example.json b/lib/config/basic_auth_config_example.json
new file mode 100644
index 0000000..05b5749
--- /dev/null
+++ b/lib/config/basic_auth_config_example.json
@@ -0,0 +1,18 @@
+{
+ "servers": {
+ "default": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "username": "keyfactor",
+ "password": "password",
+ "domain": "command",
+ "api_path": "KeyfactorAPI"
+ },
+ "server2": {
+ "host": "keyfactor2.command.kfdelivery.com",
+ "username": "keyfactor2",
+ "password": "password2",
+ "domain": "command",
+ "api_path": "Keyfactor/API"
+ }
+ }
+}
\ No newline at end of file
diff --git a/auth_config/auth_config_example.json b/lib/config/full_auth_config_example.json
similarity index 80%
rename from auth_config/auth_config_example.json
rename to lib/config/full_auth_config_example.json
index d2f4fac..4bfc7eb 100644
--- a/auth_config/auth_config_example.json
+++ b/lib/config/full_auth_config_example.json
@@ -1,9 +1,8 @@
{
"servers": {
- "default_profile": {
+ "default": {
"host": "keyfactor.command.kfdelivery.com",
- "auth_hostname": "idp.keyfactor.command.kfdelivery.com",
- "auth_port": 8444,
+ "oauth_token_url": "idp.keyfactor.command.kfdelivery.com",
"username": "keyfactor",
"password": "password",
"client_id": "client-id",
@@ -19,10 +18,9 @@
}
}
},
- "alternative_profile": {
+ "server2": {
"host": "keyfactor2.command.kfdelivery.com",
- "auth_hostname": "idp.keyfactor2.command.kfdelivery.com",
- "auth_port": 8444,
+ "oauth_token_url": "idp.keyfactor2.command.kfdelivery.com",
"username": "keyfactor2",
"password": "password2",
"client_id": "client-id2",
diff --git a/lib/config/oauth_config_example.json b/lib/config/oauth_config_example.json
new file mode 100644
index 0000000..40f097f
--- /dev/null
+++ b/lib/config/oauth_config_example.json
@@ -0,0 +1,18 @@
+{
+ "servers": {
+ "default": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "oauth_token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "api_path": "KeyfactorAPI"
+ },
+ "server2": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "oauth_token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "api_path": "KeyfactorAPI"
+ }
+ }
+}
\ No newline at end of file
From d8f274d2827aa6d1a2b74e2c087d672e2e6559ef Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 17 Oct 2024 13:01:25 -0700
Subject: [PATCH 31/57] chore: add license headers fix(core): Source
environmental variables before config validation
---
README.md | 22 +++--
auth_config/command_config.go | 14 +++
auth_config/command_config_test.go | 14 +++
auth_providers/auth_basic.go | 41 ++++++++
auth_providers/auth_basic_test.go | 86 ++++++++++++++++-
auth_providers/auth_core.go | 106 +++++++++++++++++----
auth_providers/auth_core_test.go | 14 +++
auth_providers/auth_oauth.go | 145 +++++++++++++++++++++++------
auth_providers/auth_oauth_test.go | 14 +++
go.mod | 2 +-
go.sum | 2 +
lib/main.go | 14 +++
pkg/version.go | 14 +++
13 files changed, 429 insertions(+), 59 deletions(-)
diff --git a/README.md b/README.md
index 50aa115..4265d76 100644
--- a/README.md
+++ b/README.md
@@ -16,16 +16,21 @@ Client library for authenticating to Keyfactor Command
### Global
-| Name | Description | Default |
-|-----------------------|--------------------------------------------------------------|----------------|
-| KEYFACTOR_HOSTNAME | Keyfactor Command hostname without protocol and port | |
-| KEYFACTOR_PORT | Keyfactor Command port | `443` |
-| KEYFACTOR_API_PATH | Keyfactor Command API Path | `KeyfactorAPI` |
-| KEYFACTOR_SKIP_VERIFY | Skip TLS verification when connecting to Keyfactor Command | `false` |
-| KEYFACTOR_CA_CERT | Either a file path or PEM encoded string to a CA certificate | |
+| Name | Description | Default |
+|-------------------------------|-----------------------------------------------------------------------------------------------------------------|----------------------------------------|
+| KEYFACTOR_HOSTNAME | Keyfactor Command hostname without protocol and port | |
+| KEYFACTOR_PORT | Keyfactor Command port | `443` |
+| KEYFACTOR_API_PATH | Keyfactor Command API Path | `KeyfactorAPI` |
+| KEYFACTOR_SKIP_VERIFY | Skip TLS verification when connecting to Keyfactor Command | `false` |
+| KEYFACTOR_CA_CERT | Either a file path or PEM encoded string to a CA certificate to trust when communicating with Keyfactor Command | |
+| KEYFACTOR_CLIENT_TIMEOUT | Timeout for HTTP client requests to Keyfactor Command | `60s` |
+| KEYFACTOR_AUTH_CONFIG_FILE | Path to a JSON file containing the authentication configuration | `$HOME/.keyfactor/command_config.json` |
+| KEYFACTOR_AUTH_CONFIG_PROFILE | Profile to use from the authentication configuration file | `default` |
### Basic Auth
+Currently, only Active Directory `Basic` authentication is supported.
+
| Name | Description | Default |
|--------------------|---------------------------------------------------------------------------------------------|---------|
| KEYFACTOR_USERNAME | Active Directory username to authenticate to Keyfactor Command API | |
@@ -38,8 +43,9 @@ Client library for authenticating to Keyfactor Command
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------|----------|
| KEYFACTOR_AUTH_CLIENT_ID | Keyfactor Auth Client ID | |
| KEYFACTOR_AUTH_CLIENT_SECRET | Keyfactor Auth Client Secret | |
+| KEYFACTOR_AUTH_TOKEN_URL | URL to request an access token from Keyfactor Auth | |
| KEYFACTOR_AUTH_SCOPES | Scopes to request when authenticating to Keyfactor Command API | `openid` |
-| KEYFACTOR_ACCESS_TOKEN | Access token to use to authenticate to Keyfactor Command API. This can be supplied directly or generated via client credentials | |
+| 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 | |
## Test Environment Variables
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index c696b13..e6d035e 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 authconfig
import (
diff --git a/auth_config/command_config_test.go b/auth_config/command_config_test.go
index 229799e..1b0861a 100644
--- a/auth_config/command_config_test.go
+++ b/auth_config/command_config_test.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 authconfig_test
import (
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index e308535..5f5cad9 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -19,11 +19,13 @@ import (
"fmt"
"net/http"
"os"
+ "strings"
)
const (
EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
+ EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
)
// Basic Authenticator
@@ -49,6 +51,9 @@ type CommandAuthConfigBasic struct {
// Password is the password to be used for authentication to Keyfactor Command API
Password string `json:"password,omitempty"`
+
+ // Domain is the domain of the Active Directory used to authenticate to Keyfactor Command API
+ Domain string `json:"domain,omitempty"`
}
// NewBasicAuthAuthenticatorBuilder creates a new instance of CommandAuthConfigBasic
@@ -116,6 +121,7 @@ func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
serverConfig, _ := a.CommandAuthConfig.LoadConfig(
a.CommandAuthConfig.ConfigProfile,
a.CommandAuthConfig.ConfigFilePath,
+ false,
)
if a.Username == "" {
if username, ok := os.LookupEnv(EnvKeyfactorUsername); ok {
@@ -140,6 +146,18 @@ func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
}
}
+ domainErr := a.parseUsernameDomain()
+ if domainErr != nil {
+ return domainErr
+
+ }
+
+ if a.Domain == "" {
+ if domain, ok := os.LookupEnv(EnvKeyfactorDomain); ok {
+ a.Domain = domain
+ }
+ }
+
return a.CommandAuthConfig.ValidateAuthConfig()
}
@@ -170,3 +188,26 @@ func (a *CommandAuthConfigBasic) Authenticate() error {
return a.CommandAuthConfig.Authenticate()
}
+
+// parseUsernameDomain parses the username to extract the domain if it's included in the username.
+// It supports two formats: "username@domain" and "domain\username".
+func (a *CommandAuthConfigBasic) parseUsernameDomain() error {
+ domainErr := fmt.Errorf("domain or environment variable %s is required", EnvKeyfactorDomain)
+ if strings.Contains(a.Username, "@") {
+ dSplit := strings.Split(a.Username, "@")
+ if len(dSplit) != 2 {
+ return domainErr
+ }
+ a.Username = dSplit[0] // remove domain from username
+ a.Domain = dSplit[1]
+ } else if strings.Contains(a.Username, "\\") {
+ dSplit := strings.Split(a.Username, "\\")
+ if len(dSplit) != 2 {
+ return domainErr
+ }
+ a.Domain = dSplit[0]
+ a.Username = dSplit[1] // remove domain from username
+ }
+
+ return nil
+}
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index d95f0d9..4e994ea 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -1,6 +1,21 @@
+// Copyright 2024 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"
"testing"
@@ -75,12 +90,30 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
return
}
- config := &auth_providers.CommandAuthConfigBasic{}
+ noParamsConfig := &auth_providers.CommandAuthConfigBasic{}
+ authBasicTest(t, "with complete Environmental variables", false, noParamsConfig)
- err := config.Authenticate()
- if err != nil {
- t.Fatalf("expected no error, got %v", err)
+ // Environment variables are not set
+ t.Log("Unsetting environment variables")
+ username, password, domain := exportBasicEnvVariables()
+ unsetBasicEnvVariables()
+
+ incompleteEnvConfig := &auth_providers.CommandAuthConfigBasic{}
+ authBasicTest(t, "with incomplete Environmental variables", true, incompleteEnvConfig)
+
+ usernameOnlyConfig := &auth_providers.CommandAuthConfigBasic{
+ Username: "test-username",
+ }
+ authBasicTest(t, "Username Only", true, usernameOnlyConfig)
+
+ fullParamsConfig := &auth_providers.CommandAuthConfigBasic{
+ Username: username,
+ Password: password,
+ Domain: domain,
}
+ authBasicTest(t, "w/ full params variables", false, fullParamsConfig)
+ t.Log("Resetting environment variables")
+ setBasicEnvVariables(username, password, domain)
}
func TestCommandAuthConfigBasic_Build(t *testing.T) {
@@ -103,3 +136,48 @@ func TestCommandAuthConfigBasic_Build(t *testing.T) {
t.Fatalf("expected a non-nil Authenticator")
}
}
+
+// setBasicEnvVariables sets the basic environment variables
+func setBasicEnvVariables(username, password, domain string) {
+ os.Setenv(auth_providers.EnvKeyfactorUsername, username)
+ os.Setenv(auth_providers.EnvKeyfactorPassword, password)
+ os.Setenv(auth_providers.EnvKeyfactorDomain, domain)
+}
+
+// exportBasicEnvVariables sets the basic environment variables
+func exportBasicEnvVariables() (string, string, string) {
+ username := os.Getenv(auth_providers.EnvKeyfactorUsername)
+ password := os.Getenv(auth_providers.EnvKeyfactorPassword)
+ domain := os.Getenv(auth_providers.EnvKeyfactorDomain)
+ return username, password, domain
+}
+
+// unsetBasicEnvVariables unsets the basic environment variables
+func unsetBasicEnvVariables() {
+ os.Unsetenv(auth_providers.EnvKeyfactorUsername)
+ os.Unsetenv(auth_providers.EnvKeyfactorPassword)
+ os.Unsetenv(auth_providers.EnvKeyfactorDomain)
+}
+
+func authBasicTest(t *testing.T, testName string, allowFail bool, config *auth_providers.CommandAuthConfigBasic) {
+ t.Run(
+ fmt.Sprintf("Basic Auth Test %s", testName), func(t *testing.T) {
+
+ err := config.Authenticate()
+ if allowFail {
+ if err == nil {
+ t.Errorf("Basic auth test '%s' should have failed", testName)
+ t.FailNow()
+ return
+ }
+ t.Logf("Basic auth test '%s' failed as expected with %v", testName, err)
+ return
+ }
+ if err != nil {
+ t.Errorf("Basic auth test '%s' failed with %v", testName, err)
+ t.FailNow()
+ return
+ }
+ },
+ )
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index ad49bae..e71bf6a 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -38,6 +38,7 @@ const (
DefaultAPIClientName = "APIClient"
DefaultProductVersion = "10.5.0.0"
DefaultConfigFilePath = "~/.keyfactor/command_config.json"
+ DefaultConfigProfile = "default"
DefaultClientTimeout = 60
EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
@@ -46,6 +47,8 @@ const (
EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
+ EnvKeyfactorAuthProfile = "KEYFACTOR_AUTH_CONFIG_PROFILE"
+ EnvKeyfactorConfigFile = "KEYFACTOR_AUTH_CONFIG_FILE"
EnvKeyfactorClientTimeout = "KEYFACTOR_CLIENT_TIMEOUT"
)
@@ -104,8 +107,21 @@ type CommandAuthConfig struct {
HttpClient *http.Client
}
+func cleanHostName(hostName string) string {
+ // check if hostname is a url and if so, extract the hostname
+ if strings.Contains(hostName, "://") {
+ hostName = strings.Split(hostName, "://")[1]
+ //remove any trailing paths
+ hostName = strings.Split(hostName, "/")[0]
+ // remove any trailing slashes
+ hostName = strings.TrimRight(hostName, "/")
+ }
+ return hostName
+}
+
// WithCommandHostName sets the hostname for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithCommandHostName(hostName string) *CommandAuthConfig {
+ hostName = cleanHostName(hostName)
c.CommandHostName = hostName
return c
}
@@ -144,7 +160,12 @@ func (c *CommandAuthConfig) WithHttpClient(client *http.Client) *CommandAuthConf
func (c *CommandAuthConfig) WithConfigFile(configFilePath string) *CommandAuthConfig {
if c.ConfigProfile == "" {
- c.ConfigProfile = "default"
+ // check if profile is set in environment
+ if profile, ok := os.LookupEnv(EnvKeyfactorAuthProfile); ok {
+ c.ConfigProfile = profile
+ } else {
+ c.ConfigProfile = DefaultConfigProfile
+ }
}
c.ConfigFilePath = configFilePath
@@ -153,7 +174,16 @@ func (c *CommandAuthConfig) WithConfigFile(configFilePath string) *CommandAuthCo
// WithConfigProfile sets the configuration profile for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) WithConfigProfile(profile string) *CommandAuthConfig {
- c.ConfigProfile = profile
+ if profile == "" {
+ // check if profile is set in environment
+ if profile, ok := os.LookupEnv(EnvKeyfactorAuthProfile); ok {
+ c.ConfigProfile = profile
+ } else {
+ c.ConfigProfile = DefaultConfigProfile
+ }
+ } else {
+ c.ConfigProfile = profile
+ }
return c
}
@@ -167,10 +197,10 @@ func (c *CommandAuthConfig) WithClientTimeout(timeout int) *CommandAuthConfig {
func (c *CommandAuthConfig) ValidateAuthConfig() error {
if c.CommandHostName == "" {
if hostName, ok := os.LookupEnv(EnvKeyfactorHostName); ok {
- c.CommandHostName = hostName
+ c.CommandHostName = cleanHostName(hostName)
} else {
- if c.FileConfig != nil {
- c.CommandHostName = c.FileConfig.Host
+ if c.FileConfig != nil && c.FileConfig.Host != "" {
+ c.CommandHostName = cleanHostName(c.FileConfig.Host)
} else {
return fmt.Errorf("command_host_name or environment variable %s is required", EnvKeyfactorHostName)
}
@@ -482,51 +512,91 @@ func DecodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
}
// LoadConfig loads the configuration file and returns the server configuration.
-func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string) (*authconfig.Server, error) {
+func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, silentLoad bool) (
+ *authconfig.Server,
+ error,
+) {
if configFilePath == "" {
- if c.ConfigFilePath != "" {
- configFilePath = c.ConfigFilePath
+ // check if config file is set in environment
+ if config, ok := os.LookupEnv(EnvKeyfactorConfigFile); ok {
+ configFilePath = config
} else {
configFilePath = DefaultConfigFilePath
}
+ } else {
+ c.ConfigFilePath = configFilePath
}
expandedPath, err := expandPath(configFilePath)
if err != nil {
- return nil, err
+ if !silentLoad {
+ return nil, err
+ }
+ // if silentLoad is true then eat the error and return nil
+ return nil, nil
}
file, err := os.Open(expandedPath)
if err != nil {
- return nil, err
+ if !silentLoad {
+ return nil, err
+ }
+ // if silentLoad is true then eat the error and return nil
+ return nil, nil
}
defer file.Close()
var config authconfig.Config
decoder := json.NewDecoder(file)
if jErr := decoder.Decode(&config); jErr != nil {
- return nil, jErr
+ if !silentLoad {
+ return nil, jErr
+ }
+ // if silentLoad is true then eat the error and return nil
+ return nil, nil
}
if profile == "" {
if c.ConfigProfile != "" {
profile = c.ConfigProfile
} else {
- profile = "default"
+ profile = DefaultConfigProfile
}
}
server, ok := config.Servers[profile]
if !ok {
- return nil, fmt.Errorf("profile %s not found in config file", profile)
+ if !silentLoad {
+ return nil, fmt.Errorf("profile %s not found in config file", profile)
+ }
+ // if silentLoad is true then eat the error and return nil
+ return nil, nil
}
c.FileConfig = &server
- c.CommandHostName = server.Host
- c.CommandPort = server.Port
- c.CommandAPIPath = server.APIPath
- c.CommandCACert = server.CACertPath // TODO: Implement CACert in config file
- c.SkipVerify = server.SkipTLSVerify
+ if !silentLoad {
+ c.CommandHostName = server.Host
+ c.CommandPort = server.Port
+ c.CommandAPIPath = server.APIPath
+ c.CommandCACert = server.CACertPath
+ c.SkipVerify = server.SkipTLSVerify
+ } else {
+ if c.CommandHostName == "" {
+ c.CommandHostName = server.Host
+ }
+ if c.CommandPort <= 0 {
+ c.CommandPort = server.Port
+ }
+ if c.CommandAPIPath == "" {
+ c.CommandAPIPath = server.APIPath
+ }
+ if c.CommandCACert == "" {
+ c.CommandCACert = server.CACertPath
+ }
+ if c.SkipVerify {
+ c.SkipVerify = server.SkipTLSVerify
+ }
+ }
return &server, nil
}
diff --git a/auth_providers/auth_core_test.go b/auth_providers/auth_core_test.go
index 5a3e938..efcf2a9 100644
--- a/auth_providers/auth_core_test.go
+++ b/auth_providers/auth_core_test.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 (
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index ace3776..6c455f7 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -5,8 +5,10 @@ import (
"crypto/x509"
"fmt"
"net/http"
+ "os"
"time"
+ authconfig "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
@@ -28,7 +30,7 @@ const (
EnvKeyfactorAuthTokenURL = "KEYFACTOR_AUTH_TOKEN_URL"
// EnvKeyfactorAccessToken is the environment variable used to set the access token for oauth Client credentials authentication
- EnvKeyfactorAccessToken = "KEYFACTOR_ACCESS_TOKEN"
+ EnvKeyfactorAccessToken = "KEYFACTOR_AUTH_ACCESS_TOKEN"
// EnvKeyfactorAuthAudience is the environment variable used to set the audience for oauth Client credentials
//authentication
@@ -37,12 +39,6 @@ const (
// EnvKeyfactorAuthScopes is the environment variable used to set the scopes for oauth Client credentials authentication
EnvKeyfactorAuthScopes = "KEYFACTOR_AUTH_SCOPES"
- // EnvKeyfactorAuthHostname is the environment variable used to set the hostname for oauth Client credentials authentication
- EnvKeyfactorAuthHostname = "KEYFACTOR_AUTH_HOSTNAME"
-
- // EnvKeyfactorAuthPort is the environment variable used to set the port for oauth Client credentials authentication
- EnvKeyfactorAuthPort = "KEYFACTOR_AUTH_PORT"
-
// EnvAuthCACert is a path to a CA certificate for the OAuth Client credentials authentication
EnvAuthCACert = "KEYFACTOR_AUTH_CA_CERT"
)
@@ -92,11 +88,11 @@ type CommandConfigOauth struct {
// TokenURL is the token URL for Keycloak authentication
TokenURL string `json:"token_url"`
- // AuthPort
- AuthPort string `json:"auth_port,omitempty"`
+ //// AuthPort
+ //AuthPort string `json:"auth_port,omitempty"`
- // AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
- AuthType string `json:"auth_type,omitempty"`
+ //// AuthType is the type of Keycloak auth to use such as client_credentials, password, etc.
+ //AuthType string `json:"auth_type,omitempty"`
}
// NewOAuthAuthenticatorBuilder creates a new CommandConfigOauth instance.
@@ -134,25 +130,48 @@ func (b *CommandConfigOauth) WithAudience(audience string) *CommandConfigOauth {
return b
}
-// WithCACertificatePath sets the CA certificate path for Keycloak authentication.
+// WithCaCertificatePath sets the CA certificate path for Keycloak authentication.
func (b *CommandConfigOauth) WithCaCertificatePath(caCertificatePath string) *CommandConfigOauth {
b.CACertificatePath = caCertificatePath
return b
}
-// WithCACertificates sets the CA certificates for Keycloak authentication.
+// WithCaCertificates sets the CA certificates for Keycloak authentication.
func (b *CommandConfigOauth) WithCaCertificates(caCertificates []*x509.Certificate) *CommandConfigOauth {
b.CACertificates = caCertificates
return b
}
+// WithAccessToken sets the access token for Keycloak authentication.
+func (b *CommandConfigOauth) WithAccessToken(accessToken string) *CommandConfigOauth {
+ if accessToken != "" {
+ b.AccessToken = accessToken
+ }
+
+ return b
+}
+
+// GetHttpClient returns an HTTP client for oAuth authentication.
func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
- //validate the configuration
cErr := b.ValidateAuthConfig()
if cErr != nil {
return nil, cErr
}
+ if b.AccessToken != "" {
+ return &http.Client{
+ Transport: &oauth2.Transport{
+ Base: http.DefaultTransport,
+ Source: oauth2.StaticTokenSource(
+ &oauth2.Token{
+ AccessToken: b.AccessToken,
+ TokenType: DefaultTokenPrefix,
+ },
+ ),
+ },
+ }, nil
+ }
+
config := &clientcredentials.Config{
ClientID: b.ClientID,
ClientSecret: b.ClientSecret,
@@ -166,9 +185,7 @@ func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
if b.Audience != "" {
config.EndpointParams = map[string][]string{
- "Audience": {
- b.Audience,
- },
+ "Audience": {b.Audience},
}
}
@@ -199,22 +216,93 @@ func (b *CommandConfigOauth) Build() (Authenticator, error) {
return &OAuthAuthenticator{Client: client}, nil
}
-func (b *CommandConfigOauth) ValidateAuthConfig() error {
- if b.ClientID == "" {
- return fmt.Errorf("Client ID is required")
+func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (*authconfig.Server, error) {
+ serverConfig, sErr := b.CommandAuthConfig.LoadConfig(profile, path, silentLoad)
+ if sErr != nil {
+ if !silentLoad {
+ return nil, sErr
+ }
+ // if silentLoad is true, return nil and nil
+ return nil, nil
}
- if b.ClientSecret == "" {
- return fmt.Errorf("Client secret is required")
- }
+ if !silentLoad {
+ b.ClientID = serverConfig.ClientID
+ b.ClientSecret = serverConfig.ClientSecret
+ b.TokenURL = serverConfig.OAuthTokenUrl
+ b.CACertificatePath = serverConfig.CACertPath
- if b.TokenURL == "" {
- return fmt.Errorf("token URL is required")
+ } else {
+ if b.ClientID == "" {
+ b.ClientID = serverConfig.ClientID
+ }
+
+ if b.ClientSecret == "" {
+ b.ClientSecret = serverConfig.ClientSecret
+ }
+
+ if b.TokenURL == "" {
+ b.TokenURL = serverConfig.OAuthTokenUrl
+ }
+
+ //if b.AccessToken == "" {
+ // b.AccessToken = serverConfig.AccessToken
+ //}
+
+ //if b.Audience == "" {
+ // b.Audience = serverConfig.Audience
+ //}
+ //
+ //if b.Scopes == nil || len(b.Scopes) == 0 {
+ // b.Scopes = serverConfig.Scopes
+ //}
+
+ if b.CACertificatePath == "" {
+ b.CACertificatePath = serverConfig.CACertPath
+ }
}
- //if len(b.Scopes) == 0 {
- // return fmt.Errorf("at least one scope is required")
- //}
+ return serverConfig, nil
+}
+
+func (b *CommandConfigOauth) ValidateAuthConfig() error {
+ // silently load the server config
+ _, _ = b.CommandAuthConfig.LoadConfig(
+ b.CommandAuthConfig.ConfigProfile,
+ b.CommandAuthConfig.ConfigFilePath,
+ true,
+ )
+ if b.AccessToken == "" {
+ // check if access token is set in the environment
+ if accessToken, ok := os.LookupEnv(EnvKeyfactorAccessToken); ok {
+ b.AccessToken = accessToken
+ } else {
+ // check if client ID, client secret, and token URL are provided
+ if b.ClientID == "" {
+ if clientId, idOk := os.LookupEnv(EnvKeyfactorClientID); idOk {
+ b.ClientID = clientId
+ } else {
+ return fmt.Errorf("client ID is required")
+ }
+ }
+
+ if b.ClientSecret == "" {
+ if clientSecret, sOk := os.LookupEnv(EnvKeyfactorClientSecret); sOk {
+ b.ClientSecret = clientSecret
+ } else {
+ return fmt.Errorf("client secret is required")
+ }
+ }
+
+ if b.TokenURL == "" {
+ if tokenUrl, uOk := os.LookupEnv(EnvKeyfactorAuthTokenURL); uOk {
+ b.TokenURL = tokenUrl
+ } else {
+ return fmt.Errorf("token URL is required")
+ }
+ }
+ }
+ }
return b.CommandAuthConfig.ValidateAuthConfig()
}
@@ -232,6 +320,7 @@ func (b *CommandConfigOauth) Authenticate() error {
WithClientId(b.ClientID).
WithClientSecret(b.ClientSecret).
WithTokenUrl(b.TokenURL).
+ WithAccessToken(b.AccessToken).
Build()
if err != nil {
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 82a79a8..0962f37 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 (
diff --git a/go.mod b/go.mod
index 3c45212..e83a7c5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,6 @@ module github.com/Keyfactor/keyfactor-auth-client-go
go 1.22
require (
- golang.org/x/oauth2 v0.22.0
+ golang.org/x/oauth2 v0.23.0
gopkg.in/yaml.v2 v2.4.0
)
diff --git a/go.sum b/go.sum
index 17be337..b65756f 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
diff --git a/lib/main.go b/lib/main.go
index c1f2736..e41bf2c 100644
--- a/lib/main.go
+++ b/lib/main.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 main
import (
diff --git a/pkg/version.go b/pkg/version.go
index 25ba0e1..1bc3d80 100644
--- a/pkg/version.go
+++ b/pkg/version.go
@@ -1,3 +1,17 @@
+// Copyright 2024 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 pkg
var (
From 79969ab79b97bb7ec4cceaca691994dd3b87f37f Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 17 Oct 2024 13:59:14 -0700
Subject: [PATCH 32/57] fix(core): Add `WithDomain` to basicauth chore(tests):
Add negative tests for basic auth
---
auth_providers/auth_basic.go | 6 ++++++
auth_providers/auth_basic_test.go | 18 ++++++++++++++++++
2 files changed, 24 insertions(+)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 5f5cad9..5befc45 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -73,6 +73,12 @@ func (a *CommandAuthConfigBasic) WithPassword(password string) *CommandAuthConfi
return a
}
+// WithDomain sets the domain for authentication
+func (a *CommandAuthConfigBasic) WithDomain(domain string) *CommandAuthConfigBasic {
+ a.Domain = domain
+ return a
+}
+
// GetHttpClient returns the http client
func (a *CommandAuthConfigBasic) GetHttpClient() (*http.Client, error) {
//validate the configuration
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 4e994ea..25f45a7 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -18,6 +18,7 @@ import (
"fmt"
"net/http"
"os"
+ "strings"
"testing"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
@@ -112,6 +113,23 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
Domain: domain,
}
authBasicTest(t, "w/ full params variables", false, fullParamsConfig)
+
+ noDomainConfig := &auth_providers.CommandAuthConfigBasic{
+ Username: username,
+ Password: password,
+ }
+ authBasicTest(t, "w/ no domain", false, noDomainConfig)
+
+ // remove domain from username
+ usernameNoDomain := strings.Split(username, "@")[0]
+ t.Logf("Username without domain: %s", usernameNoDomain)
+ usernameNoDomainConfig := &auth_providers.CommandAuthConfigBasic{
+ Username: usernameNoDomain,
+ Password: password,
+ }
+ //TODO: This really SHOULD fail, but it doesn't and the auth header is sent without the domain yet it still authenticates
+ authBasicTest(t, "w/o domain and no domain in username", false, usernameNoDomainConfig)
+
t.Log("Resetting environment variables")
setBasicEnvVariables(username, password, domain)
}
From 8b1ac49de14e9f777c4a6bbab6d97e51880c81d5 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 18 Oct 2024 11:25:30 -0700
Subject: [PATCH 33/57] chore(tests): Finish basicauth tests
---
auth_config/command_config.go | 23 +++++++
auth_providers/auth_basic.go | 15 ++++-
auth_providers/auth_basic_test.go | 105 ++++++++++++++++++++++++++++--
auth_providers/auth_core.go | 12 ++--
4 files changed, 143 insertions(+), 12 deletions(-)
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index e6d035e..da19475 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -62,6 +62,29 @@ type Config struct {
// return yaml.Unmarshal(data, c)
//}
+func NewConfig() *Config {
+ return &Config{
+ Servers: make(map[string]Server),
+ }
+}
+
+// ReadConfigFromJSON reads a Config configuration from a JSON file.
+func ReadConfigFromJSON(filePath string) (*Config, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var config Config
+ decoder := json.NewDecoder(file)
+ if err := decoder.Decode(&config); err != nil {
+ return nil, err
+ }
+
+ return &config, nil
+}
+
// ReadServerFromJSON reads a Server configuration from a JSON file.
func ReadServerFromJSON(filePath string) (*Server, error) {
file, err := os.Open(filePath)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 5befc45..0713300 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -124,11 +124,21 @@ func (a *CommandAuthConfigBasic) Build() (Authenticator, error) {
// ValidateAuthConfig validates the configuration
func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
- serverConfig, _ := a.CommandAuthConfig.LoadConfig(
+ silentLoad := true
+ if a.CommandAuthConfig.ConfigProfile != "" {
+ silentLoad = false
+ } else if a.CommandAuthConfig.ConfigFilePath != "" {
+ silentLoad = false
+ }
+ serverConfig, cErr := a.CommandAuthConfig.LoadConfig(
a.CommandAuthConfig.ConfigProfile,
a.CommandAuthConfig.ConfigFilePath,
- false,
+ silentLoad,
)
+ if !silentLoad && cErr != nil {
+ return cErr
+ }
+
if a.Username == "" {
if username, ok := os.LookupEnv(EnvKeyfactorUsername); ok {
a.Username = username
@@ -178,6 +188,7 @@ func (a *CommandAuthConfigBasic) Authenticate() error {
authy, err := NewBasicAuthAuthenticatorBuilder().
WithUsername(a.Username).
WithPassword(a.Password).
+ WithDomain(a.Domain).
Build()
if err != nil {
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 25f45a7..86abba3 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -21,6 +21,7 @@ import (
"strings"
"testing"
+ auth_config "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
@@ -91,6 +92,35 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
return
}
+ userHome, hErr := os.UserHomeDir()
+ if hErr != nil {
+ userHome = os.Getenv("HOME")
+ }
+
+ configFilePath := fmt.Sprintf("%s/%s", userHome, auth_providers.DefaultConfigFilePath)
+ configFromFile, cErr := auth_config.ReadConfigFromJSON(configFilePath)
+ if cErr != nil {
+ t.Errorf("unable to load auth config from file %s: %v", configFilePath, cErr)
+ }
+
+ if configFromFile == nil || configFromFile.Servers == nil {
+ t.Errorf("invalid config file %s", configFilePath)
+ t.FailNow()
+ }
+
+ // Delete the config file
+ t.Logf("Deleting config file: %s", configFilePath)
+ os.Remove(configFilePath)
+ defer func() {
+ // Write the config file back
+ t.Logf("Writing config file: %s", configFilePath)
+ fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ if fErr != nil {
+ t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
+ }
+ }()
+
+ t.Log("Testing Basic Auth with Environmental variables")
noParamsConfig := &auth_providers.CommandAuthConfigBasic{}
authBasicTest(t, "with complete Environmental variables", false, noParamsConfig)
@@ -98,15 +128,30 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
t.Log("Unsetting environment variables")
username, password, domain := exportBasicEnvVariables()
unsetBasicEnvVariables()
+ defer func() {
+ t.Log("Resetting environment variables")
+ setBasicEnvVariables(username, password, domain)
+ }()
+ t.Log("Testing Basic Auth with no Environmental variables")
incompleteEnvConfig := &auth_providers.CommandAuthConfigBasic{}
- authBasicTest(t, "with incomplete Environmental variables", true, incompleteEnvConfig)
+ incompleteEnvConfigExpectedError := "username or environment variable KEYFACTOR_USERNAME is required"
+ authBasicTest(
+ t,
+ "with incomplete Environmental variables",
+ true,
+ incompleteEnvConfig,
+ incompleteEnvConfigExpectedError,
+ )
+ t.Log("Testing auth with only username")
usernameOnlyConfig := &auth_providers.CommandAuthConfigBasic{
Username: "test-username",
}
- authBasicTest(t, "Username Only", true, usernameOnlyConfig)
+ usernameOnlyConfigExceptedError := "password or environment variable KEYFACTOR_PASSWORD is required"
+ authBasicTest(t, "username only", true, usernameOnlyConfig, usernameOnlyConfigExceptedError)
+ t.Log("Testing auth with w/ full params variables")
fullParamsConfig := &auth_providers.CommandAuthConfigBasic{
Username: username,
Password: password,
@@ -114,13 +159,23 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
}
authBasicTest(t, "w/ full params variables", false, fullParamsConfig)
+ t.Log("Testing auth with w/ full params variables")
+ fullParamsinvalidPassConfig := &auth_providers.CommandAuthConfigBasic{
+ Username: username,
+ Password: "invalid-password",
+ Domain: domain,
+ }
+ invalidCredsExpectedError := []string{"401", "Unauthorized", "Access is denied due to invalid credentials"}
+ authBasicTest(t, "w/ full params & invalid pass", true, fullParamsinvalidPassConfig, invalidCredsExpectedError...)
+
+ t.Log("Testing auth with w/ no domain")
noDomainConfig := &auth_providers.CommandAuthConfigBasic{
Username: username,
Password: password,
}
authBasicTest(t, "w/ no domain", false, noDomainConfig)
- // remove domain from username
+ t.Log("Testing auth with w/ no domain and no domain in username")
usernameNoDomain := strings.Split(username, "@")[0]
t.Logf("Username without domain: %s", usernameNoDomain)
usernameNoDomainConfig := &auth_providers.CommandAuthConfigBasic{
@@ -130,8 +185,34 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
//TODO: This really SHOULD fail, but it doesn't and the auth header is sent without the domain yet it still authenticates
authBasicTest(t, "w/o domain and no domain in username", false, usernameNoDomainConfig)
- t.Log("Resetting environment variables")
- setBasicEnvVariables(username, password, domain)
+ // Write the config file back
+ t.Logf("Writing config file: %s", configFilePath)
+ fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ if fErr != nil {
+ t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
+ }
+
+ t.Log("Testing Basic Auth with valid implicit config file")
+ wConfigFile := &auth_providers.CommandAuthConfigBasic{}
+ authBasicTest(t, "with valid implicit config file", false, wConfigFile)
+
+ t.Log("Testing Basic Auth with invalid profile implicit config file")
+ invProfile := &auth_providers.CommandAuthConfigBasic{}
+ invProfile.WithConfigProfile("invalid-profile")
+ expectedError := []string{"profile", "invalid-profile", "not found"}
+ authBasicTest(t, "with invalid profile implicit config file", true, invProfile, expectedError...)
+
+ t.Log("Testing Basic Auth with invalid creds implicit config file")
+ invProfileCreds := &auth_providers.CommandAuthConfigBasic{}
+ invProfileCreds.WithConfigProfile("invalid_username")
+ authBasicTest(t, "with invalid creds implicit config file", true, invProfileCreds, invalidCredsExpectedError...)
+
+ t.Log("Testing Basic Auth with invalid config file path")
+ invFilePath := &auth_providers.CommandAuthConfigBasic{}
+ invFilePath.WithConfigFile("invalid-file-path")
+ invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"}
+ authBasicTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...)
+
}
func TestCommandAuthConfigBasic_Build(t *testing.T) {
@@ -177,7 +258,10 @@ func unsetBasicEnvVariables() {
os.Unsetenv(auth_providers.EnvKeyfactorDomain)
}
-func authBasicTest(t *testing.T, testName string, allowFail bool, config *auth_providers.CommandAuthConfigBasic) {
+func authBasicTest(
+ t *testing.T, testName string, allowFail bool, config *auth_providers.CommandAuthConfigBasic,
+ errorContains ...string,
+) {
t.Run(
fmt.Sprintf("Basic Auth Test %s", testName), func(t *testing.T) {
@@ -188,6 +272,15 @@ func authBasicTest(t *testing.T, testName string, allowFail bool, config *auth_p
t.FailNow()
return
}
+ if len(errorContains) > 0 {
+ for _, ec := range errorContains {
+ if !strings.Contains(err.Error(), ec) {
+ t.Errorf("Basic auth test '%s' failed with unexpected error %v", testName, err)
+ t.FailNow()
+ return
+ }
+ }
+ }
t.Logf("Basic auth test '%s' failed as expected with %v", testName, err)
return
}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index e71bf6a..bf45a5a 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -37,7 +37,7 @@ const (
DefaultAPIVersion = "1"
DefaultAPIClientName = "APIClient"
DefaultProductVersion = "10.5.0.0"
- DefaultConfigFilePath = "~/.keyfactor/command_config.json"
+ DefaultConfigFilePath = ".keyfactor/command_config.json"
DefaultConfigProfile = "default"
DefaultClientTimeout = 60
@@ -176,8 +176,8 @@ func (c *CommandAuthConfig) WithConfigFile(configFilePath string) *CommandAuthCo
func (c *CommandAuthConfig) WithConfigProfile(profile string) *CommandAuthConfig {
if profile == "" {
// check if profile is set in environment
- if profile, ok := os.LookupEnv(EnvKeyfactorAuthProfile); ok {
- c.ConfigProfile = profile
+ if p, ok := os.LookupEnv(EnvKeyfactorAuthProfile); ok {
+ c.ConfigProfile = p
} else {
c.ConfigProfile = DefaultConfigProfile
}
@@ -521,7 +521,11 @@ func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, si
if config, ok := os.LookupEnv(EnvKeyfactorConfigFile); ok {
configFilePath = config
} else {
- configFilePath = DefaultConfigFilePath
+ homedir, err := os.UserHomeDir()
+ if err != nil {
+ homedir = os.Getenv("HOME")
+ }
+ configFilePath = fmt.Sprintf("%s/%s", homedir, DefaultConfigFilePath)
}
} else {
c.ConfigFilePath = configFilePath
From f15a3e172bb060c5dc1460833933d8ef54eee273 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 18 Oct 2024 12:42:26 -0700
Subject: [PATCH 34/57] chore(tests): Finish oauth tests
---
auth_config/command_config.go | 2 +-
auth_providers/auth_basic_test.go | 25 +++-
auth_providers/auth_oauth.go | 42 ++++++-
auth_providers/auth_oauth_test.go | 194 ++++++++++++++++++++++++++++--
4 files changed, 243 insertions(+), 20 deletions(-)
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index da19475..2addac1 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -32,7 +32,7 @@ type Server struct {
Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` // Domain is the domain for authentication.
ClientID string `json:"client_id,omitempty" yaml:"client_id,omitempty"` // ClientID is the client ID for OAuth.
ClientSecret string `json:"client_secret,omitempty" yaml:"client_secret,omitempty"` // ClientSecret is the client secret for OAuth.
- OAuthTokenUrl string `json:"oauth_token_url,omitempty" yaml:"auth_hostname,omitempty"` // OAuthTokenUrl is full URL for OAuth token request endpoint.
+ OAuthTokenUrl string `json:"token_url,omitempty" yaml:"token_url,omitempty"` // OAuthTokenUrl is full URL for OAuth token request endpoint.
APIPath string `json:"api_path,omitempty" yaml:"api_path,omitempty"` // APIPath is the API path.
AuthProvider AuthProvider `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` // AuthProvider contains the authentication provider details.
SkipTLSVerify bool `json:"skip_tls_verify,omitempty" yaml:"skip_tls_verify,omitempty"` // TLSVerify determines whether to verify the TLS certificate.
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 86abba3..7f3af4a 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -124,6 +124,12 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
noParamsConfig := &auth_providers.CommandAuthConfigBasic{}
authBasicTest(t, "with complete Environmental variables", false, noParamsConfig)
+ t.Log("Testing Basic Auth with invalid config file path")
+ invFilePath := &auth_providers.CommandAuthConfigBasic{}
+ invFilePath.WithConfigFile("invalid-file-path")
+ invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"}
+ authBasicTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...)
+
// Environment variables are not set
t.Log("Unsetting environment variables")
username, password, domain := exportBasicEnvVariables()
@@ -207,11 +213,20 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
invProfileCreds.WithConfigProfile("invalid_username")
authBasicTest(t, "with invalid creds implicit config file", true, invProfileCreds, invalidCredsExpectedError...)
- t.Log("Testing Basic Auth with invalid config file path")
- invFilePath := &auth_providers.CommandAuthConfigBasic{}
- invFilePath.WithConfigFile("invalid-file-path")
- invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"}
- authBasicTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...)
+ t.Log("Testing Basic Auth with invalid Command host implicit config file")
+ invHostConfig := &auth_providers.CommandAuthConfigBasic{}
+ invHostConfig.WithConfigProfile("invalid_host")
+ invHostExpectedError := []string{"no such host"}
+ authBasicTest(
+ t, "with invalid Command host implicit config file", true, invHostConfig,
+ invHostExpectedError...,
+ )
+
+ //t.Log("Testing Basic Auth with invalid config file path")
+ //invFilePath := &auth_providers.CommandAuthConfigBasic{}
+ //invFilePath.WithConfigFile("invalid-file-path")
+ //invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"}
+ //authBasicTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...)
}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 6c455f7..8fb786a 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -266,12 +266,24 @@ func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (
}
func (b *CommandConfigOauth) ValidateAuthConfig() error {
- // silently load the server config
- _, _ = b.CommandAuthConfig.LoadConfig(
+
+ silentLoad := true
+ if b.CommandAuthConfig.ConfigProfile != "" {
+ silentLoad = false
+ } else if b.CommandAuthConfig.ConfigFilePath != "" {
+ silentLoad = false
+ }
+
+ serverConfig, cErr := b.CommandAuthConfig.LoadConfig(
b.CommandAuthConfig.ConfigProfile,
b.CommandAuthConfig.ConfigFilePath,
- true,
+ silentLoad,
)
+
+ if !silentLoad && cErr != nil {
+ return cErr
+ }
+
if b.AccessToken == "" {
// check if access token is set in the environment
if accessToken, ok := os.LookupEnv(EnvKeyfactorAccessToken); ok {
@@ -282,7 +294,11 @@ func (b *CommandConfigOauth) ValidateAuthConfig() error {
if clientId, idOk := os.LookupEnv(EnvKeyfactorClientID); idOk {
b.ClientID = clientId
} else {
- return fmt.Errorf("client ID is required")
+ if serverConfig != nil && serverConfig.ClientID != "" {
+ b.ClientID = serverConfig.ClientID
+ } else {
+ return fmt.Errorf("client ID or environment variable %s is required", EnvKeyfactorClientID)
+ }
}
}
@@ -290,7 +306,14 @@ func (b *CommandConfigOauth) ValidateAuthConfig() error {
if clientSecret, sOk := os.LookupEnv(EnvKeyfactorClientSecret); sOk {
b.ClientSecret = clientSecret
} else {
- return fmt.Errorf("client secret is required")
+ if serverConfig != nil && serverConfig.ClientSecret != "" {
+ b.ClientSecret = serverConfig.ClientSecret
+ } else {
+ return fmt.Errorf(
+ "client secret or environment variable %s is required",
+ EnvKeyfactorClientSecret,
+ )
+ }
}
}
@@ -298,7 +321,14 @@ func (b *CommandConfigOauth) ValidateAuthConfig() error {
if tokenUrl, uOk := os.LookupEnv(EnvKeyfactorAuthTokenURL); uOk {
b.TokenURL = tokenUrl
} else {
- return fmt.Errorf("token URL is required")
+ if serverConfig != nil && serverConfig.OAuthTokenUrl != "" {
+ b.TokenURL = serverConfig.OAuthTokenUrl
+ } else {
+ return fmt.Errorf(
+ "token URL or environment variable %s is required",
+ EnvKeyfactorAuthTokenURL,
+ )
+ }
}
}
}
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 0962f37..a017493 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -15,10 +15,13 @@
package auth_providers_test
import (
+ "fmt"
"net/http"
"os"
+ "strings"
"testing"
+ auth_config "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
@@ -89,17 +92,135 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
t.Skip("Skipping TestOAuthAuthenticator_GetHttpClient")
return
}
- config := &auth_providers.CommandConfigOauth{
- ClientID: os.Getenv(auth_providers.EnvKeyfactorClientID),
- ClientSecret: os.Getenv(auth_providers.EnvKeyfactorClientSecret),
- TokenURL: os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL),
- Scopes: []string{"openid", "profile", "email"},
+ userHome, hErr := os.UserHomeDir()
+ if hErr != nil {
+ userHome = os.Getenv("HOME")
}
- err := config.Authenticate()
- if err != nil {
- t.Fatalf("expected no error, got %v", err)
+ configFilePath := fmt.Sprintf("%s/%s", userHome, auth_providers.DefaultConfigFilePath)
+ configFromFile, cErr := auth_config.ReadConfigFromJSON(configFilePath)
+ if cErr != nil {
+ t.Errorf("unable to load auth config from file %s: %v", configFilePath, cErr)
+ }
+
+ if configFromFile == nil || configFromFile.Servers == nil {
+ t.Errorf("invalid config file %s", configFilePath)
+ t.FailNow()
+ }
+
+ // Delete the config file
+ t.Logf("Deleting config file: %s", configFilePath)
+ os.Remove(configFilePath)
+ defer func() {
+ // Write the config file back
+ t.Logf("Writing config file: %s", configFilePath)
+ fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ if fErr != nil {
+ t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
+ }
+ }()
+
+ t.Log("Testing oAuth with Environmental variables")
+ noParamsConfig := &auth_providers.CommandConfigOauth{}
+ authOauthTest(t, "with complete Environmental variables", false, noParamsConfig)
+
+ t.Log("Testing oAuth with invalid config file path")
+ invFilePath := &auth_providers.CommandConfigOauth{}
+ invFilePath.WithConfigFile("invalid-file-path")
+ invalidPathExpectedError := []string{"no such file or directory", "invalid-file-path"}
+ authOauthTest(t, "with invalid config file PATH", true, invFilePath, invalidPathExpectedError...)
+
+ // Environment variables are not set
+ t.Log("Unsetting environment variables")
+ clientID, clientSecret, tokenURL := exportOAuthEnvVariables()
+ unsetOAuthEnvVariables()
+ defer func() {
+ t.Log("Resetting environment variables")
+ setOAuthEnvVariables(clientID, clientSecret, tokenURL)
+ }()
+
+ t.Log("Testing oAuth with no Environmental variables")
+ incompleteEnvConfig := &auth_providers.CommandConfigOauth{}
+ incompleteEnvConfigExpectedError := fmt.Sprintf(
+ "client ID or environment variable %s is required",
+ auth_providers.EnvKeyfactorClientID,
+ )
+ authOauthTest(
+ t,
+ "with incomplete Environmental variables",
+ true,
+ incompleteEnvConfig,
+ incompleteEnvConfigExpectedError,
+ )
+
+ t.Log("Testing auth with only clientID")
+ clientIDOnlyConfig := &auth_providers.CommandConfigOauth{
+ ClientID: "test-client-id",
+ }
+ clientIDOnlyConfigExceptedError := fmt.Sprintf(
+ "client secret or environment variable %s is required",
+ auth_providers.EnvKeyfactorClientSecret,
+ )
+ authOauthTest(t, "clientID only", true, clientIDOnlyConfig, clientIDOnlyConfigExceptedError)
+
+ t.Log("Testing auth with w/ full params variables")
+ fullParamsConfig := &auth_providers.CommandConfigOauth{
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ TokenURL: tokenURL,
+ }
+ authOauthTest(t, "w/ full params variables", false, fullParamsConfig)
+
+ t.Log("Testing auth with w/ full params & invalid pass")
+ fullParamsInvalidPassConfig := &auth_providers.CommandConfigOauth{
+ ClientID: clientID,
+ ClientSecret: "invalid-client-secret",
+ TokenURL: tokenURL,
+ }
+ invalidCredsExpectedError := []string{
+ "oauth2", "unauthorized_client", "Invalid client or Invalid client credentials",
+ }
+ authOauthTest(t, "w/ full params & invalid pass", true, fullParamsInvalidPassConfig, invalidCredsExpectedError...)
+
+ t.Log("Testing auth with w/ no tokenURL")
+ noTokenURLConfig := &auth_providers.CommandConfigOauth{
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ }
+ noTokenURLExpectedError := fmt.Sprintf(
+ "token URL or environment variable %s is required",
+ auth_providers.EnvKeyfactorAuthTokenURL,
+ )
+ authOauthTest(t, "w/ no tokenURL", true, noTokenURLConfig, noTokenURLExpectedError)
+
+ // Write the config file back
+ t.Logf("Writing config file: %s", configFilePath)
+ fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ if fErr != nil {
+ t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
+
+ t.Log("Testing oAuth with valid implicit config file")
+ wConfigFile := &auth_providers.CommandConfigOauth{}
+ wConfigFile.WithConfigProfile("oauth")
+ authOauthTest(t, "with valid implicit config file", false, wConfigFile)
+
+ t.Log("Testing oAuth with invalid profile implicit config file")
+ invProfile := &auth_providers.CommandConfigOauth{}
+ invProfile.WithConfigProfile("invalid-profile")
+ expectedError := []string{"profile", "invalid-profile", "not found"}
+ authOauthTest(t, "with invalid profile implicit config file", true, invProfile, expectedError...)
+
+ t.Log("Testing oAuth with invalid creds implicit config file")
+ invProfileCreds := &auth_providers.CommandConfigOauth{}
+ invProfileCreds.WithConfigProfile("oauth_invalid_creds")
+ 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")
+ invHostExpectedError := []string{"no such host"}
+ authOauthTest(t, "with invalid creds implicit config file", true, invCmdHost, invHostExpectedError...)
}
func TestCommandConfigOauth_Build(t *testing.T) {
@@ -124,3 +245,60 @@ func TestCommandConfigOauth_Build(t *testing.T) {
t.Fatalf("expected a non-nil Authenticator")
}
}
+
+func authOauthTest(
+ t *testing.T, testName string, allowFail bool, config *auth_providers.CommandConfigOauth,
+ errorContains ...string,
+) {
+ t.Run(
+ fmt.Sprintf("oAuth Auth Test %s", testName), func(t *testing.T) {
+
+ err := config.Authenticate()
+ if allowFail {
+ if err == nil {
+ t.Errorf("oAuth 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("oAuth auth test '%s' failed with unexpected error %v", testName, err)
+ t.FailNow()
+ return
+ }
+ }
+ }
+ t.Logf("oAuth auth test '%s' failed as expected with %v", testName, err)
+ return
+ }
+ if err != nil {
+ t.Errorf("oAuth auth test '%s' failed with %v", testName, err)
+ t.FailNow()
+ return
+ }
+ },
+ )
+}
+
+// setOAuthEnvVariables sets the oAuth environment variables
+func setOAuthEnvVariables(client_id, client_secret, token_url string) {
+ os.Setenv(auth_providers.EnvKeyfactorClientID, client_id)
+ os.Setenv(auth_providers.EnvKeyfactorClientSecret, client_secret)
+ os.Setenv(auth_providers.EnvKeyfactorAuthTokenURL, token_url)
+}
+
+// exportOAuthEnvVariables sets the oAuth environment variables
+func exportOAuthEnvVariables() (string, string, string) {
+ client_id := os.Getenv(auth_providers.EnvKeyfactorClientID)
+ client_secret := os.Getenv(auth_providers.EnvKeyfactorClientSecret)
+ token_url := os.Getenv(auth_providers.EnvKeyfactorAuthTokenURL)
+ return client_id, client_secret, token_url
+}
+
+// unsetOAuthEnvVariables unsets the oAuth environment variables
+func unsetOAuthEnvVariables() {
+ os.Unsetenv(auth_providers.EnvKeyfactorClientID)
+ os.Unsetenv(auth_providers.EnvKeyfactorClientSecret)
+ os.Unsetenv(auth_providers.EnvKeyfactorAuthTokenURL)
+}
From 6dc5efdf2c43fa901c8fee1ce4ddaaa816a4f04f Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Fri, 18 Oct 2024 12:55:20 -0700
Subject: [PATCH 35/57] fix(ci): Add ability to test command auth config file
---
.github/config/environments.tf | 2 ++
.github/workflows/go_tests.yml | 8 +++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
index 0b42a3f..d26fab7 100644
--- a/.github/config/environments.tf
+++ b/.github/config/environments.tf
@@ -6,6 +6,7 @@ module "keyfactor_github_test_environment_ad_10_5_0" {
keyfactor_hostname = var.keyfactor_hostname_10_5_0
keyfactor_username = var.keyfactor_username_10_5_0
keyfactor_password = var.keyfactor_password_10_5_0
+ keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
}
# module "keyfactor_github_test_environment_11_5_0_kc" {
@@ -30,4 +31,5 @@ module "keyfactor_github_test_environment_12_3_0_kc" {
keyfactor_client_id = var.keyfactor_client_id_12_3_0
keyfactor_client_secret = var.keyfactor_client_secret_12_3_0
keyfactor_tls_skip_verify = true
+ keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
}
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 1d9096d..337caf9 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -22,10 +22,16 @@ jobs:
go-version: 1.22
- name: Run tests
- run: go test -v -cover ./auth_providers/...
+ run: |
+ if [ -n "${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}" ]; then
+ mkdir -p ~/.keyfactor
+ echo "${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}" | base64 --decode > ~/.keyfactor/command_config.json
+ fi
+ go test -v -cover ./auth_providers/...
env:
KEYFACTOR_PASSWORD: ${{ secrets.KEYFACTOR_PASSWORD }}
KEYFACTOR_USERNAME: ${{ secrets.KEYFACTOR_USERNAME }}
+ KEYFACTOR_AUTH_CONFIG_B64: ${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
KEYFACTOR_HOSTNAME: ${{ vars.KEYFACTOR_HOSTNAME }}
From e02a467f90b36ee6eafe74062c61e5346d2b3005 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 21 Oct 2024 06:10:36 -0700
Subject: [PATCH 36/57] fix(conf): Add `GetAuthType`
---
auth_config/command_config.go | 18 +++++++++++++++---
auth_config/command_config_test.go | 4 +---
tag.sh | 2 +-
3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index 2addac1..b17edd3 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package authconfig
+package auth_config
import (
"encoding/json"
@@ -36,8 +36,9 @@ type Server struct {
APIPath string `json:"api_path,omitempty" yaml:"api_path,omitempty"` // APIPath is the API path.
AuthProvider AuthProvider `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` // AuthProvider contains the authentication provider details.
SkipTLSVerify bool `json:"skip_tls_verify,omitempty" yaml:"skip_tls_verify,omitempty"` // TLSVerify determines whether to verify the TLS certificate.
- CACertPath string `json:"ca_cert_path,omitempty" yaml:"ca_cert_path,
-omitempty"` // CACertPath is the path to the CA certificate to trust.
+ 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.
+
}
// AuthProvider represents the authentication provider configuration.
@@ -208,3 +209,14 @@ func MergeConfigFromFile(filePath string, config *Config) error {
return nil
}
+
+func (s *Server) GetAuthType() string {
+ if s.ClientID != "" && s.ClientSecret != "" {
+ s.AuthType = "oauth"
+ } else if s.Username != "" && s.Password != "" {
+ s.AuthType = "basic"
+ } else {
+ s.AuthType = ""
+ }
+ return s.AuthType
+}
diff --git a/auth_config/command_config_test.go b/auth_config/command_config_test.go
index 1b0861a..8f5ec4e 100644
--- a/auth_config/command_config_test.go
+++ b/auth_config/command_config_test.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package authconfig_test
+package auth_config_test
import (
"encoding/json"
@@ -20,8 +20,6 @@ import (
"testing"
"gopkg.in/yaml.v2"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
)
func TestReadServerFromJSON(t *testing.T) {
diff --git a/tag.sh b/tag.sh
index f1086ab..42ccefa 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.8
+RC_VERSION=rc.9
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From aa2b90d34137f5144191b30070ca756324bd6bef Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 21 Oct 2024 06:26:00 -0700
Subject: [PATCH 37/57] fix(conf): Add `UserAgent` param
---
auth_providers/auth_core.go | 3 +++
tag.sh | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index bf45a5a..f24ca3d 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -103,6 +103,9 @@ type CommandAuthConfig struct {
// HttpClientTimeout is the timeout for the http Client
HttpClientTimeout int `json:"client_timeout"`
+ // UserAgent is the user agent to be used for authentication to Keyfactor Command API
+ UserAgent string `json:"user_agent,omitempty"`
+
// HttpClient is the http Client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
}
diff --git a/tag.sh b/tag.sh
index 42ccefa..bfc802c 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.9
+RC_VERSION=rc.10
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From bf152565e5a1f35294414a5039e6c68a4a012cde Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 21 Oct 2024 06:30:00 -0700
Subject: [PATCH 38/57] fix(conf): Add `Debug` param
---
auth_providers/auth_core.go | 3 +++
tag.sh | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index f24ca3d..d5669b0 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -106,6 +106,9 @@ type CommandAuthConfig struct {
// UserAgent is the user agent to be used for authentication to Keyfactor Command API
UserAgent string `json:"user_agent,omitempty"`
+ // Debug
+ Debug bool `json:"debug,omitempty"`
+
// HttpClient is the http Client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
}
diff --git a/tag.sh b/tag.sh
index bfc802c..08d9cf4 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.10
+RC_VERSION=rc.11
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 2ebd039126680fbe1091b849ee9f83a1048477b6 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 21 Oct 2024 08:17:06 -0700
Subject: [PATCH 39/57] feat(auth): Add `GetServerConfig` that returns
`*authconfig.Server`
---
auth_providers/auth_basic.go | 22 ++++++++++++++++++++++
auth_providers/auth_core.go | 6 +++---
auth_providers/auth_oauth.go | 16 ++++++++++++++++
tag.sh | 2 +-
4 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 0713300..6d77afb 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -20,6 +20,8 @@ import (
"net/http"
"os"
"strings"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
)
const (
@@ -228,3 +230,23 @@ func (a *CommandAuthConfigBasic) parseUsernameDomain() error {
return nil
}
+
+// GetServerConfig returns the server configuration
+func (a *CommandAuthConfigBasic) GetServerConfig() *auth_config.Server {
+ server := auth_config.Server{
+ Host: a.CommandHostName,
+ Port: a.CommandPort,
+ Username: a.Username,
+ Password: a.Password,
+ Domain: a.Domain,
+ ClientID: "",
+ ClientSecret: "",
+ OAuthTokenUrl: "",
+ APIPath: a.CommandAPIPath,
+ //AuthProvider: auth_config.AuthProvider{},
+ SkipTLSVerify: a.SkipVerify,
+ CACertPath: a.CommandCACert,
+ AuthType: "basic",
+ }
+ return &server
+}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index d5669b0..4e2d728 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -83,13 +83,13 @@ type CommandAuthConfig struct {
AuthHeader string `json:"auth_header"`
// CommandHostName is the hostname of the Keyfactor Command API
- CommandHostName string `json:"command_host_name"`
+ CommandHostName string `json:"host"`
// CommandPort is the port of the Keyfactor Command API
- CommandPort int `json:"command_port"`
+ CommandPort int `json:"port"`
// CommandAPIPath is the path of the Keyfactor Command API, default is "KeyfactorAPI"
- CommandAPIPath string `json:"command_api_path"`
+ CommandAPIPath string `json:"api_path"`
// CommandAPIVersion is the version of the Keyfactor Command API, default is "1"
CommandVersion string `json:"command_version"`
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 8fb786a..00bcc61 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -372,3 +372,19 @@ func (b *CommandConfigOauth) Authenticate() error {
return nil
}
+
+func (b *CommandConfigOauth) GetServerConfig() *authconfig.Server {
+ server := authconfig.Server{
+ Host: b.CommandHostName,
+ Port: b.CommandPort,
+ ClientID: b.ClientID,
+ ClientSecret: b.ClientSecret,
+ OAuthTokenUrl: b.TokenURL,
+ APIPath: b.CommandAPIPath,
+ //AuthProvider: authconfig.AuthProvider{},
+ SkipTLSVerify: b.SkipVerify,
+ CACertPath: b.CommandCACert,
+ AuthType: "oauth",
+ }
+ return &server
+}
diff --git a/tag.sh b/tag.sh
index 08d9cf4..d18b07d 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.11
+RC_VERSION=rc.13
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 6c5dd1a18a954f64d8c7e2c267828e9a1bdf57ab Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 21 Oct 2024 08:21:25 -0700
Subject: [PATCH 40/57] feat(auth): Add `GetServerConfig` that returns
`*authconfig.Server`
---
auth_providers/auth_core.go | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 4e2d728..b4569d4 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -621,3 +621,22 @@ func expandPath(path string) (string, error) {
}
return path, nil
}
+
+func (c *CommandAuthConfig) GetServerConfig() *authconfig.Server {
+ server := authconfig.Server{
+ Host: c.CommandHostName,
+ Port: c.CommandPort,
+ Username: "",
+ Password: "",
+ Domain: "",
+ ClientID: "",
+ ClientSecret: "",
+ OAuthTokenUrl: "",
+ APIPath: c.CommandAPIPath,
+ AuthProvider: authconfig.AuthProvider{},
+ SkipTLSVerify: c.SkipVerify,
+ CACertPath: c.CommandCACert,
+ AuthType: "",
+ }
+ return &server
+}
From 08cbfe230161949409b48765324d659ae7e09664 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 22 Oct 2024 15:27:16 -0700
Subject: [PATCH 41/57] feat(conf): Add ability to construct client configs
based on Server params
---
auth_config/command_config.go | 88 +++++++++++++++++++++++++++++++----
tag.sh | 2 +-
2 files changed, 79 insertions(+), 11 deletions(-)
diff --git a/auth_config/command_config.go b/auth_config/command_config.go
index b17edd3..a700d94 100644
--- a/auth_config/command_config.go
+++ b/auth_config/command_config.go
@@ -20,6 +20,8 @@ import (
"os"
"gopkg.in/yaml.v2"
+
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
// Server represents the server configuration for authentication.
@@ -53,16 +55,6 @@ type Config struct {
Servers map[string]Server `json:"servers,omitempty" yaml:"servers,omitempty"` // Servers is a map of server configurations.
}
-// UnmarshalJSON unmarshals the JSON data into the Config struct.
-//func (c *Config) UnmarshalJSON(data []byte) error {
-// return json.Unmarshal(data, c)
-//}
-
-// UnmarshalYAML unmarshals the YAML data into the Config struct.
-//func (c *Config) UnmarshalYAML(data []byte) error {
-// return yaml.Unmarshal(data, c)
-//}
-
func NewConfig() *Config {
return &Config{
Servers: make(map[string]Server),
@@ -86,6 +78,21 @@ func ReadConfigFromJSON(filePath string) (*Config, error) {
return &config, nil
}
+// ReadConfigFromYAML reads a Config configuration from a YAML file.
+func ReadConfigFromYAML(filePath string) (*Config, error) {
+ file, err := os.ReadFile(filePath)
+ if err != nil {
+ return nil, err
+ }
+
+ var config Config
+ if err := yaml.Unmarshal(file, &config); err != nil {
+ return nil, err
+ }
+
+ return &config, nil
+}
+
// ReadServerFromJSON reads a Server configuration from a JSON file.
func ReadServerFromJSON(filePath string) (*Server, error) {
file, err := os.Open(filePath)
@@ -220,3 +227,64 @@ func (s *Server) GetAuthType() string {
}
return s.AuthType
}
+
+// GetBasicAuthClientConfig returns the basic auth configuration for the client.
+func (s *Server) GetBasicAuthClientConfig() (*auth_providers.CommandAuthConfigBasic, error) {
+ configType := s.GetAuthType()
+ if configType != "basic" {
+ return nil, fmt.Errorf("invalid auth type: %s", configType)
+ }
+
+ baseConfig := auth_providers.CommandAuthConfig{}
+ baseConfig.
+ WithCommandHostName(s.Host).
+ WithCommandPort(s.Port).
+ WithCommandAPIPath(s.APIPath).
+ WithCommandCACert(s.CACertPath).
+ WithSkipVerify(s.SkipTLSVerify)
+
+ basicConfig := auth_providers.CommandAuthConfigBasic{
+ CommandAuthConfig: baseConfig,
+ }
+ basicConfig.
+ WithUsername(s.Username).
+ WithPassword(s.Password).
+ WithDomain(s.Domain).
+ Build()
+
+ vErr := basicConfig.ValidateAuthConfig()
+ if vErr != nil {
+ return nil, vErr
+ }
+ return &basicConfig, nil
+}
+
+// GetOAuthClientConfig returns the OAuth configuration for the client.
+func (s *Server) GetOAuthClientConfig() (*auth_providers.CommandConfigOauth, error) {
+ configType := s.GetAuthType()
+ if configType != "oauth" {
+ return nil, fmt.Errorf("invalid auth type: %s", configType)
+ }
+ baseConfig := auth_providers.CommandAuthConfig{}
+ baseConfig.
+ WithCommandHostName(s.Host).
+ WithCommandPort(s.Port).
+ WithCommandAPIPath(s.APIPath).
+ WithCommandCACert(s.CACertPath).
+ WithSkipVerify(s.SkipTLSVerify)
+
+ oauthConfig := auth_providers.CommandConfigOauth{
+ CommandAuthConfig: baseConfig,
+ }
+ oauthConfig.
+ WithClientId(s.ClientID).
+ WithClientSecret(s.ClientSecret).
+ WithTokenUrl(s.OAuthTokenUrl).
+ Build()
+
+ vErr := oauthConfig.ValidateAuthConfig()
+ if vErr != nil {
+ return nil, vErr
+ }
+ return &oauthConfig, nil
+}
diff --git a/tag.sh b/tag.sh
index d18b07d..f82972b 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.13
+RC_VERSION=rc.15
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From df84e8928f4d4b764663b7bc24d04ba19f85ed3b Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 24 Oct 2024 14:18:39 -0700
Subject: [PATCH 42/57] feat(auth): Basic auth to include domain in username if
present
---
auth_providers/auth_basic.go | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 6d77afb..5da1f21 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -90,7 +90,12 @@ func (a *CommandAuthConfigBasic) GetHttpClient() (*http.Client, error) {
}
// Encode the username and password in Base64
- auth := a.Username + ":" + a.Password
+ var auth string
+ if a.Domain != "" {
+ auth = a.Domain + "\\" + a.Username + ":" + a.Password
+ } else {
+ auth = a.Username + ":" + a.Password
+ }
encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
// Create a custom RoundTripper
From ae2aaf9775d8173559d0610140140282d2de5a77 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 24 Oct 2024 14:56:57 -0700
Subject: [PATCH 43/57] fix(pkg): Flatten package structure
---
auth_providers/auth_basic.go | 8 +--
auth_providers/auth_core.go | 14 ++--
auth_providers/auth_oauth.go | 9 ++-
auth_providers/auth_oauth_test.go | 7 +-
.../command_config.go | 16 ++---
.../command_config_test.go | 65 ++++++++++---------
tag.sh | 2 +-
7 files changed, 57 insertions(+), 64 deletions(-)
rename {auth_config => auth_providers}/command_config.go (94%)
rename {auth_config => auth_providers}/command_config_test.go (83%)
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 5da1f21..38829a7 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -20,8 +20,6 @@ import (
"net/http"
"os"
"strings"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
)
const (
@@ -237,8 +235,8 @@ func (a *CommandAuthConfigBasic) parseUsernameDomain() error {
}
// GetServerConfig returns the server configuration
-func (a *CommandAuthConfigBasic) GetServerConfig() *auth_config.Server {
- server := auth_config.Server{
+func (a *CommandAuthConfigBasic) GetServerConfig() *Server {
+ server := Server{
Host: a.CommandHostName,
Port: a.CommandPort,
Username: a.Username,
@@ -248,7 +246,7 @@ func (a *CommandAuthConfigBasic) GetServerConfig() *auth_config.Server {
ClientSecret: "",
OAuthTokenUrl: "",
APIPath: a.CommandAPIPath,
- //AuthProvider: auth_config.AuthProvider{},
+ //AuthProvider: AuthProvider{},
SkipTLSVerify: a.SkipVerify,
CACertPath: a.CommandCACert,
AuthType: "basic",
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index b4569d4..f2cd6ef 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -27,8 +27,6 @@ import (
"strconv"
"strings"
"time"
-
- authconfig "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
)
const (
@@ -77,7 +75,7 @@ type CommandAuthConfig struct {
ConfigFilePath string
// FileConfig
- FileConfig *authconfig.Server
+ FileConfig *Server
// AuthHeader is the header to be used for authentication to Keyfactor Command API
AuthHeader string `json:"auth_header"`
@@ -519,7 +517,7 @@ func DecodePEMBytes(buf []byte) ([]*pem.Block, []byte, error) {
// LoadConfig loads the configuration file and returns the server configuration.
func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, silentLoad bool) (
- *authconfig.Server,
+ *Server,
error,
) {
if configFilePath == "" {
@@ -555,7 +553,7 @@ func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, si
}
defer file.Close()
- var config authconfig.Config
+ var config Config
decoder := json.NewDecoder(file)
if jErr := decoder.Decode(&config); jErr != nil {
if !silentLoad {
@@ -622,8 +620,8 @@ func expandPath(path string) (string, error) {
return path, nil
}
-func (c *CommandAuthConfig) GetServerConfig() *authconfig.Server {
- server := authconfig.Server{
+func (c *CommandAuthConfig) GetServerConfig() *Server {
+ server := Server{
Host: c.CommandHostName,
Port: c.CommandPort,
Username: "",
@@ -633,7 +631,7 @@ func (c *CommandAuthConfig) GetServerConfig() *authconfig.Server {
ClientSecret: "",
OAuthTokenUrl: "",
APIPath: c.CommandAPIPath,
- AuthProvider: authconfig.AuthProvider{},
+ AuthProvider: AuthProvider{},
SkipTLSVerify: c.SkipVerify,
CACertPath: c.CommandCACert,
AuthType: "",
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 00bcc61..639f1bc 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -8,7 +8,6 @@ import (
"os"
"time"
- authconfig "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
@@ -216,7 +215,7 @@ func (b *CommandConfigOauth) Build() (Authenticator, error) {
return &OAuthAuthenticator{Client: client}, nil
}
-func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (*authconfig.Server, error) {
+func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (*Server, error) {
serverConfig, sErr := b.CommandAuthConfig.LoadConfig(profile, path, silentLoad)
if sErr != nil {
if !silentLoad {
@@ -373,15 +372,15 @@ func (b *CommandConfigOauth) Authenticate() error {
return nil
}
-func (b *CommandConfigOauth) GetServerConfig() *authconfig.Server {
- server := authconfig.Server{
+func (b *CommandConfigOauth) GetServerConfig() *Server {
+ server := Server{
Host: b.CommandHostName,
Port: b.CommandPort,
ClientID: b.ClientID,
ClientSecret: b.ClientSecret,
OAuthTokenUrl: b.TokenURL,
APIPath: b.CommandAPIPath,
- //AuthProvider: authconfig.AuthProvider{},
+ //AuthProvider: AuthProvider{},
SkipTLSVerify: b.SkipVerify,
CACertPath: b.CommandCACert,
AuthType: "oauth",
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index a017493..0ee1667 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -21,7 +21,6 @@ import (
"strings"
"testing"
- auth_config "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
@@ -98,7 +97,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
}
configFilePath := fmt.Sprintf("%s/%s", userHome, auth_providers.DefaultConfigFilePath)
- configFromFile, cErr := auth_config.ReadConfigFromJSON(configFilePath)
+ configFromFile, cErr := auth_providers.ReadConfigFromJSON(configFilePath)
if cErr != nil {
t.Errorf("unable to load auth config from file %s: %v", configFilePath, cErr)
}
@@ -114,7 +113,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
defer func() {
// Write the config file back
t.Logf("Writing config file: %s", configFilePath)
- fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ fErr := auth_providers.WriteConfigToJSON(configFilePath, configFromFile)
if fErr != nil {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
@@ -195,7 +194,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
// Write the config file back
t.Logf("Writing config file: %s", configFilePath)
- fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ fErr := auth_providers.WriteConfigToJSON(configFilePath, configFromFile)
if fErr != nil {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
diff --git a/auth_config/command_config.go b/auth_providers/command_config.go
similarity index 94%
rename from auth_config/command_config.go
rename to auth_providers/command_config.go
index a700d94..3d5d382 100644
--- a/auth_config/command_config.go
+++ b/auth_providers/command_config.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package auth_config
+package auth_providers
import (
"encoding/json"
@@ -20,8 +20,6 @@ import (
"os"
"gopkg.in/yaml.v2"
-
- "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
// Server represents the server configuration for authentication.
@@ -229,13 +227,13 @@ func (s *Server) GetAuthType() string {
}
// GetBasicAuthClientConfig returns the basic auth configuration for the client.
-func (s *Server) GetBasicAuthClientConfig() (*auth_providers.CommandAuthConfigBasic, error) {
+func (s *Server) GetBasicAuthClientConfig() (*CommandAuthConfigBasic, error) {
configType := s.GetAuthType()
if configType != "basic" {
return nil, fmt.Errorf("invalid auth type: %s", configType)
}
- baseConfig := auth_providers.CommandAuthConfig{}
+ baseConfig := CommandAuthConfig{}
baseConfig.
WithCommandHostName(s.Host).
WithCommandPort(s.Port).
@@ -243,7 +241,7 @@ func (s *Server) GetBasicAuthClientConfig() (*auth_providers.CommandAuthConfigBa
WithCommandCACert(s.CACertPath).
WithSkipVerify(s.SkipTLSVerify)
- basicConfig := auth_providers.CommandAuthConfigBasic{
+ basicConfig := CommandAuthConfigBasic{
CommandAuthConfig: baseConfig,
}
basicConfig.
@@ -260,12 +258,12 @@ func (s *Server) GetBasicAuthClientConfig() (*auth_providers.CommandAuthConfigBa
}
// GetOAuthClientConfig returns the OAuth configuration for the client.
-func (s *Server) GetOAuthClientConfig() (*auth_providers.CommandConfigOauth, error) {
+func (s *Server) GetOAuthClientConfig() (*CommandConfigOauth, error) {
configType := s.GetAuthType()
if configType != "oauth" {
return nil, fmt.Errorf("invalid auth type: %s", configType)
}
- baseConfig := auth_providers.CommandAuthConfig{}
+ baseConfig := CommandAuthConfig{}
baseConfig.
WithCommandHostName(s.Host).
WithCommandPort(s.Port).
@@ -273,7 +271,7 @@ func (s *Server) GetOAuthClientConfig() (*auth_providers.CommandConfigOauth, err
WithCommandCACert(s.CACertPath).
WithSkipVerify(s.SkipTLSVerify)
- oauthConfig := auth_providers.CommandConfigOauth{
+ oauthConfig := CommandConfigOauth{
CommandAuthConfig: baseConfig,
}
oauthConfig.
diff --git a/auth_config/command_config_test.go b/auth_providers/command_config_test.go
similarity index 83%
rename from auth_config/command_config_test.go
rename to auth_providers/command_config_test.go
index 8f5ec4e..0a43068 100644
--- a/auth_config/command_config_test.go
+++ b/auth_providers/command_config_test.go
@@ -12,19 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package auth_config_test
+package auth_providers_test
import (
"encoding/json"
"os"
"testing"
+ "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
"gopkg.in/yaml.v2"
)
func TestReadServerFromJSON(t *testing.T) {
filePath := "test_config.json"
- server := &authconfig.Server{
+ server := &auth_providers.Server{
Host: "localhost",
Port: 8080,
OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
@@ -36,13 +37,13 @@ func TestReadServerFromJSON(t *testing.T) {
APIPath: "api",
}
- err := authconfig.WriteServerToJSON(filePath, server)
+ err := auth_providers.WriteServerToJSON(filePath, server)
if err != nil {
t.Fatalf("failed to write server to JSON: %v", err)
}
defer os.Remove(filePath)
- readServer, err := authconfig.ReadServerFromJSON(filePath)
+ readServer, err := auth_providers.ReadServerFromJSON(filePath)
if err != nil {
t.Fatalf("failed to read server from JSON: %v", err)
}
@@ -54,7 +55,7 @@ func TestReadServerFromJSON(t *testing.T) {
func TestWriteServerToJSON(t *testing.T) {
filePath := "test_server.json"
- server := &authconfig.Server{
+ server := &auth_providers.Server{
Host: "localhost",
Port: 8080,
OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
@@ -66,7 +67,7 @@ func TestWriteServerToJSON(t *testing.T) {
APIPath: "api",
}
- err := authconfig.WriteServerToJSON(filePath, server)
+ err := auth_providers.WriteServerToJSON(filePath, server)
if err != nil {
t.Fatalf("failed to write server to JSON: %v", err)
}
@@ -77,7 +78,7 @@ func TestWriteServerToJSON(t *testing.T) {
t.Fatalf("failed to read file: %v", err)
}
- var readServer authconfig.Server
+ var readServer auth_providers.Server
err = json.Unmarshal(file, &readServer)
if err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
@@ -90,7 +91,7 @@ func TestWriteServerToJSON(t *testing.T) {
func TestReadServerFromYAML(t *testing.T) {
filePath := "test_server.yaml"
- server := &authconfig.Server{
+ server := &auth_providers.Server{
Host: "localhost",
Port: 8080,
OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
@@ -102,13 +103,13 @@ func TestReadServerFromYAML(t *testing.T) {
APIPath: "api",
}
- err := authconfig.WriteServerToYAML(filePath, server)
+ err := auth_providers.WriteServerToYAML(filePath, server)
if err != nil {
t.Fatalf("failed to write server to YAML: %v", err)
}
defer os.Remove(filePath)
- readServer, err := authconfig.ReadServerFromYAML(filePath)
+ readServer, err := auth_providers.ReadServerFromYAML(filePath)
if err != nil {
t.Fatalf("failed to read server from YAML: %v", err)
}
@@ -120,7 +121,7 @@ func TestReadServerFromYAML(t *testing.T) {
func TestWriteServerToYAML(t *testing.T) {
filePath := "test_server.yaml"
- server := &authconfig.Server{
+ server := &auth_providers.Server{
Host: "localhost",
Port: 8080,
OAuthTokenUrl: "https://auth.localhost:8443/openid/token",
@@ -132,7 +133,7 @@ func TestWriteServerToYAML(t *testing.T) {
APIPath: "api",
}
- err := authconfig.WriteServerToYAML(filePath, server)
+ err := auth_providers.WriteServerToYAML(filePath, server)
if err != nil {
t.Fatalf("failed to write server to YAML: %v", err)
}
@@ -143,7 +144,7 @@ func TestWriteServerToYAML(t *testing.T) {
t.Fatalf("failed to read file: %v", err)
}
- var readServer authconfig.Server
+ var readServer auth_providers.Server
err = yaml.Unmarshal(file, &readServer)
if err != nil {
t.Fatalf("failed to unmarshal YAML: %v", err)
@@ -156,8 +157,8 @@ func TestWriteServerToYAML(t *testing.T) {
func TestMergeConfigFromFile(t *testing.T) {
filePath := "test_config.json"
- config := &authconfig.Config{
- Servers: map[string]authconfig.Server{
+ config := &auth_providers.Config{
+ Servers: map[string]auth_providers.Server{
"server1": {
Host: "localhost",
Port: 8080,
@@ -165,14 +166,14 @@ func TestMergeConfigFromFile(t *testing.T) {
},
}
- err := authconfig.WriteConfigToJSON(filePath, config)
+ err := auth_providers.WriteConfigToJSON(filePath, config)
if err != nil {
t.Fatalf("failed to write config to JSON: %v", err)
}
defer os.Remove(filePath)
- newConfig := &authconfig.Config{
- Servers: map[string]authconfig.Server{
+ newConfig := &auth_providers.Config{
+ Servers: map[string]auth_providers.Server{
"server2": {
Host: "remotehost",
Port: 9090,
@@ -180,7 +181,7 @@ func TestMergeConfigFromFile(t *testing.T) {
},
}
- err = authconfig.MergeConfigFromFile(filePath, newConfig)
+ err = auth_providers.MergeConfigFromFile(filePath, newConfig)
if err != nil {
t.Fatalf("failed to merge config from file: %v", err)
}
@@ -200,8 +201,8 @@ func TestMergeConfigFromFile(t *testing.T) {
func TestReadFullAuthConfigExample(t *testing.T) {
filePath := "../lib/config/full_auth_config_example.json"
- expectedConfig := &authconfig.Config{
- Servers: map[string]authconfig.Server{
+ expectedConfig := &auth_providers.Config{
+ Servers: map[string]auth_providers.Server{
"default": {
Host: "keyfactor.command.kfdelivery.com",
OAuthTokenUrl: "idp.keyfactor.command.kfdelivery.com",
@@ -211,7 +212,7 @@ func TestReadFullAuthConfigExample(t *testing.T) {
ClientSecret: "client-secret",
Domain: "command",
APIPath: "KeyfactorAPI",
- AuthProvider: authconfig.AuthProvider{
+ AuthProvider: auth_providers.AuthProvider{
Type: "azid",
Profile: "azure",
Parameters: map[string]interface{}{
@@ -229,7 +230,7 @@ func TestReadFullAuthConfigExample(t *testing.T) {
ClientSecret: "client-secret2",
Domain: "command",
APIPath: "KeyfactorAPI",
- AuthProvider: authconfig.AuthProvider{
+ AuthProvider: auth_providers.AuthProvider{
Type: "azid",
Profile: "azure",
Parameters: map[string]interface{}{
@@ -246,7 +247,7 @@ func TestReadFullAuthConfigExample(t *testing.T) {
t.Fatalf("failed to read file: %v", err)
}
- var config authconfig.Config
+ var config auth_providers.Config
err = json.Unmarshal(file, &config)
if err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
@@ -259,8 +260,8 @@ func TestReadFullAuthConfigExample(t *testing.T) {
func TestReadOAuthConfigExample(t *testing.T) {
filePath := "../lib/config/oauth_config_example.json"
- expectedConfig := &authconfig.Config{
- Servers: map[string]authconfig.Server{
+ expectedConfig := &auth_providers.Config{
+ Servers: map[string]auth_providers.Server{
"default": {
Host: "keyfactor.command.kfdelivery.com",
OAuthTokenUrl: "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
@@ -283,7 +284,7 @@ func TestReadOAuthConfigExample(t *testing.T) {
t.Fatalf("failed to read file: %v", err)
}
- var config authconfig.Config
+ var config auth_providers.Config
err = json.Unmarshal(file, &config)
if err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
@@ -296,8 +297,8 @@ func TestReadOAuthConfigExample(t *testing.T) {
func TestReadBasicAuthConfigExample(t *testing.T) {
filePath := "../lib/config/basic_auth_config_example.json"
- expectedConfig := &authconfig.Config{
- Servers: map[string]authconfig.Server{
+ expectedConfig := &auth_providers.Config{
+ Servers: map[string]auth_providers.Server{
"default": {
Host: "keyfactor.command.kfdelivery.com",
Username: "keyfactor",
@@ -320,7 +321,7 @@ func TestReadBasicAuthConfigExample(t *testing.T) {
t.Fatalf("failed to read file: %v", err)
}
- var config authconfig.Config
+ var config auth_providers.Config
err = json.Unmarshal(file, &config)
if err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
@@ -331,7 +332,7 @@ func TestReadBasicAuthConfigExample(t *testing.T) {
}
}
-func compareConfigs(a, b *authconfig.Config) bool {
+func compareConfigs(a, b *auth_providers.Config) bool {
if len(a.Servers) != len(b.Servers) {
return false
}
@@ -344,7 +345,7 @@ func compareConfigs(a, b *authconfig.Config) bool {
return true
}
-func compareServers(a, b *authconfig.Server) bool {
+func compareServers(a, b *auth_providers.Server) bool {
return a.Host == b.Host &&
a.Port == b.Port &&
a.OAuthTokenUrl == b.OAuthTokenUrl &&
diff --git a/tag.sh b/tag.sh
index f82972b..7c04e1f 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.15
+RC_VERSION=rc.17
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 892ec73467fad6debbb0158dba15d2524e7f0135 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 24 Oct 2024 15:08:15 -0700
Subject: [PATCH 44/57] fix(tests): Basic auth tests don't ref `auth_config`
anymore
---
auth_providers/auth_basic_test.go | 7 +++----
tag.sh | 2 +-
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 7f3af4a..5c404ae 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -21,7 +21,6 @@ import (
"strings"
"testing"
- auth_config "github.com/Keyfactor/keyfactor-auth-client-go/auth_config"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
)
@@ -98,7 +97,7 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
}
configFilePath := fmt.Sprintf("%s/%s", userHome, auth_providers.DefaultConfigFilePath)
- configFromFile, cErr := auth_config.ReadConfigFromJSON(configFilePath)
+ configFromFile, cErr := auth_providers.ReadConfigFromJSON(configFilePath)
if cErr != nil {
t.Errorf("unable to load auth config from file %s: %v", configFilePath, cErr)
}
@@ -114,7 +113,7 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
defer func() {
// Write the config file back
t.Logf("Writing config file: %s", configFilePath)
- fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ fErr := auth_providers.WriteConfigToJSON(configFilePath, configFromFile)
if fErr != nil {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
@@ -193,7 +192,7 @@ func TestCommandAuthConfigBasic_Authenticate(t *testing.T) {
// Write the config file back
t.Logf("Writing config file: %s", configFilePath)
- fErr := auth_config.WriteConfigToJSON(configFilePath, configFromFile)
+ fErr := auth_providers.WriteConfigToJSON(configFilePath, configFromFile)
if fErr != nil {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
diff --git a/tag.sh b/tag.sh
index 7c04e1f..c350f84 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,4 +1,4 @@
-RC_VERSION=rc.17
+RC_VERSION=rc.18
TAG_VERSION_1=v0.0.1-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
From 0249a731b4d04328534362cb18a257e7c4ecae49 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 28 Oct 2024 10:49:12 -0700
Subject: [PATCH 45/57] chore(docs): Add example usage for auth providers.
---
auth_providers/auth_basic.go | 39 ++++++++++++--
auth_providers/auth_core.go | 88 ++++++++++++++++++++++++++------
auth_providers/auth_oauth.go | 51 ++++++++++++++++--
auth_providers/command_config.go | 54 ++++++++++++++++++++
integration-manifest.json | 11 ++++
tag.sh | 4 +-
6 files changed, 222 insertions(+), 25 deletions(-)
create mode 100644 integration-manifest.json
diff --git a/auth_providers/auth_basic.go b/auth_providers/auth_basic.go
index 38829a7..eb3f58a 100644
--- a/auth_providers/auth_basic.go
+++ b/auth_providers/auth_basic.go
@@ -23,9 +23,14 @@ import (
)
const (
+ // EnvKeyfactorUsername is the environment variable for the Keyfactor hostname
EnvKeyfactorUsername = "KEYFACTOR_USERNAME"
+
+ // EnvKeyfactorPassword is the environment variable for the Keyfactor password
EnvKeyfactorPassword = "KEYFACTOR_PASSWORD"
- EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
+
+ // EnvKeyfactorDomain is the environment variable for the Keyfactor domain
+ EnvKeyfactorDomain = "KEYFACTOR_DOMAIN"
)
// Basic Authenticator
@@ -127,7 +132,7 @@ func (a *CommandAuthConfigBasic) Build() (Authenticator, error) {
return &BasicAuthAuthenticator{Client: client}, nil
}
-// ValidateAuthConfig validates the configuration
+// ValidateAuthConfig validates the basic authentication configuration.
func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
silentLoad := true
if a.CommandAuthConfig.ConfigProfile != "" {
@@ -182,7 +187,7 @@ func (a *CommandAuthConfigBasic) ValidateAuthConfig() error {
return a.CommandAuthConfig.ValidateAuthConfig()
}
-// Authenticate authenticates the user
+// Authenticate authenticates the request using basic authentication.
func (a *CommandAuthConfigBasic) Authenticate() error {
cErr := a.ValidateAuthConfig()
if cErr != nil {
@@ -253,3 +258,31 @@ func (a *CommandAuthConfigBasic) GetServerConfig() *Server {
}
return &server
}
+
+// Example usage of CommandAuthConfigBasic
+//
+// This example demonstrates how to use CommandAuthConfigBasic to authenticate a user.
+//
+// func ExampleCommandAuthConfigBasic_Authenticate() {
+// authConfig := &CommandAuthConfigBasic{
+// CommandAuthConfig: CommandAuthConfig{
+// ConfigFilePath: "/path/to/config.json",
+// ConfigProfile: "default",
+// CommandHostName: "exampleHost",
+// CommandPort: 443,
+// CommandAPIPath: "/api/v1",
+// CommandCACert: "/path/to/ca-cert.pem",
+// SkipVerify: true,
+// },
+// Username: "exampleUser",
+// Password: "examplePassword",
+// Domain: "exampleDomain",
+// }
+//
+// err := authConfig.Authenticate()
+// if err != nil {
+// fmt.Println("Authentication failed:", err)
+// } else {
+// fmt.Println("Authentication successful")
+// }
+// }
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index f2cd6ef..f8f9420 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -30,23 +30,55 @@ import (
)
const (
- DefaultCommandPort = 443
+ // DefaultCommandPort is the default port for Keyfactor Command API
+ DefaultCommandPort = 443
+
+ // DefaultCommandAPIPath is the default path for Keyfactor Command API
DefaultCommandAPIPath = "KeyfactorAPI"
- DefaultAPIVersion = "1"
- DefaultAPIClientName = "APIClient"
+
+ // DefaultAPIVersion is the default version for Keyfactor Command API
+ DefaultAPIVersion = "1"
+
+ // DefaultAPIClientName is the default client name for Keyfactor Command API
+ DefaultAPIClientName = "APIClient"
+
+ // DefaultProductVersion is the default product version for Keyfactor Command API
DefaultProductVersion = "10.5.0.0"
+
+ // DefaultConfigFilePath is the default path for the configuration file
DefaultConfigFilePath = ".keyfactor/command_config.json"
- DefaultConfigProfile = "default"
- DefaultClientTimeout = 60
-
- EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
- EnvKeyfactorPort = "KEYFACTOR_PORT"
- EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
- EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
- EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
- EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
- EnvKeyfactorAuthProfile = "KEYFACTOR_AUTH_CONFIG_PROFILE"
- EnvKeyfactorConfigFile = "KEYFACTOR_AUTH_CONFIG_FILE"
+
+ // DefaultConfigProfile is the default profile for the configuration file
+ DefaultConfigProfile = "default"
+
+ // DefaultClientTimeout is the default timeout for the http Client
+ DefaultClientTimeout = 60
+
+ // EnvKeyfactorHostName is the environment variable for the Keyfactor Command hostname
+ EnvKeyfactorHostName = "KEYFACTOR_HOSTNAME"
+
+ // EnvKeyfactorPort is the environment variable for the Keyfactor Command http(s) port
+ EnvKeyfactorPort = "KEYFACTOR_PORT"
+
+ // EnvKeyfactorAPIPath is the environment variable for the Keyfactor Command API path
+ EnvKeyfactorAPIPath = "KEYFACTOR_API_PATH"
+
+ // EnvKeyfactorSkipVerify is the environment variable for skipping TLS verification when communicating with Keyfactor Command
+ EnvKeyfactorSkipVerify = "KEYFACTOR_SKIP_VERIFY"
+
+ // EnvKeyfactorCACert is the environment variable for the CA certificate to be used for TLS verification when communicating with Keyfactor Command API
+ EnvKeyfactorCACert = "KEYFACTOR_CA_CERT"
+
+ // EnvKeyfactorAuthProvider is the environment variable for the authentication provider to be used for Keyfactor Command API
+ EnvKeyfactorAuthProvider = "KEYFACTOR_AUTH_PROVIDER"
+
+ // EnvKeyfactorAuthProfile is the environment variable for the profile of the configuration file
+ EnvKeyfactorAuthProfile = "KEYFACTOR_AUTH_CONFIG_PROFILE"
+
+ // EnvKeyfactorConfigFile is the environment variable for the configuration file to reference for connecting to the Keyfactor Command API
+ EnvKeyfactorConfigFile = "KEYFACTOR_AUTH_CONFIG_FILE"
+
+ // EnvKeyfactorClientTimeout is the environment variable for the timeout for the http Client
EnvKeyfactorClientTimeout = "KEYFACTOR_CLIENT_TIMEOUT"
)
@@ -111,8 +143,9 @@ type CommandAuthConfig struct {
HttpClient *http.Client
}
+// cleanHostName cleans the hostname for authentication to Keyfactor Command API.
func cleanHostName(hostName string) string {
- // check if hostname is a url and if so, extract the hostname
+ // check if hostname is a URL and if so, extract the hostname
if strings.Contains(hostName, "://") {
hostName = strings.Split(hostName, "://")[1]
//remove any trailing paths
@@ -620,6 +653,7 @@ func expandPath(path string) (string, error) {
return path, nil
}
+// GetServerConfig returns the server configuration.
func (c *CommandAuthConfig) GetServerConfig() *Server {
server := Server{
Host: c.CommandHostName,
@@ -638,3 +672,27 @@ func (c *CommandAuthConfig) GetServerConfig() *Server {
}
return &server
}
+
+// Example usage of CommandAuthConfig
+//
+// This example demonstrates how to use CommandAuthConfig to authenticate to the Keyfactor Command API.
+//
+// func ExampleCommandAuthConfig_Authenticate() {
+// authConfig := &CommandAuthConfig{
+// ConfigFilePath: "/path/to/config.json",
+// ConfigProfile: "default",
+// CommandHostName: "exampleHost",
+// CommandPort: 443,
+// CommandAPIPath: "/api/v1",
+// CommandCACert: "/path/to/ca-cert.pem",
+// SkipVerify: true,
+// HttpClientTimeout: 60,
+// }
+//
+// err := authConfig.Authenticate()
+// if err != nil {
+// fmt.Println("Authentication failed:", err)
+// } else {
+// fmt.Println("Authentication successful")
+// }
+// }
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 639f1bc..c113614 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -50,11 +50,14 @@ type OAuthAuthenticator struct {
Client *http.Client
}
+// GetHttpClient returns the http client
func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
return a.Client, nil
}
+// CommandConfigOauth represents the configuration needed for authentication to Keyfactor Command API using OAuth2.
type CommandConfigOauth struct {
+ // CommandAuthConfig is a reference to the base configuration needed for authentication to Keyfactor Command API
CommandAuthConfig
// ClientID is the Client ID for Keycloak authentication
@@ -76,16 +79,16 @@ type CommandConfigOauth struct {
CACertificates []*x509.Certificate `json:"-"`
// AccessToken is the access token for Keycloak authentication
- AccessToken string `json:"access_token;omitempty"`
+ AccessToken string `json:"access_token,omitempty"`
// RefreshToken is the refresh token for Keycloak authentication
- RefreshToken string `json:"refresh_token;omitempty"`
+ RefreshToken string `json:"refresh_token,omitempty"`
// Expiry is the expiry time of the access token
- Expiry time.Time `json:"expiry;omitempty"`
+ Expiry time.Time `json:"expiry,omitempty"`
// TokenURL is the token URL for Keycloak authentication
- TokenURL string `json:"token_url"`
+ TokenURL string `json:"token_url,omitempty"`
//// AuthPort
//AuthPort string `json:"auth_port,omitempty"`
@@ -178,7 +181,7 @@ func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
Scopes: b.Scopes,
}
- if b.Scopes == nil || len(b.Scopes) == 0 {
+ if len(b.Scopes) == 0 {
b.Scopes = []string{"openid", "profile", "email"}
}
@@ -205,6 +208,7 @@ func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
}
+// Build creates an OAuth authenticator.
func (b *CommandConfigOauth) Build() (Authenticator, error) {
client, cErr := b.GetHttpClient()
@@ -215,6 +219,7 @@ func (b *CommandConfigOauth) Build() (Authenticator, error) {
return &OAuthAuthenticator{Client: client}, nil
}
+// LoadConfig loads the configuration for Keyfactor Command API using OAuth2.
func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (*Server, error) {
serverConfig, sErr := b.CommandAuthConfig.LoadConfig(profile, path, silentLoad)
if sErr != nil {
@@ -264,6 +269,7 @@ func (b *CommandConfigOauth) LoadConfig(profile, path string, silentLoad bool) (
return serverConfig, nil
}
+// ValidateAuthConfig validates the configuration for Keyfactor Command API using OAuth2.
func (b *CommandConfigOauth) ValidateAuthConfig() error {
silentLoad := true
@@ -336,6 +342,7 @@ func (b *CommandConfigOauth) ValidateAuthConfig() error {
return b.CommandAuthConfig.ValidateAuthConfig()
}
+// Authenticate authenticates to Keyfactor Command API using OAuth2.
func (b *CommandConfigOauth) Authenticate() error {
// validate auth config
@@ -372,6 +379,7 @@ func (b *CommandConfigOauth) Authenticate() error {
return nil
}
+// GetServerConfig returns the server configuration for Keyfactor Command API using OAuth2.
func (b *CommandConfigOauth) GetServerConfig() *Server {
server := Server{
Host: b.CommandHostName,
@@ -387,3 +395,36 @@ func (b *CommandConfigOauth) GetServerConfig() *Server {
}
return &server
}
+
+// Example usage of CommandConfigOauth
+//
+// This example demonstrates how to use CommandConfigOauth to authenticate to the Keyfactor Command API using OAuth2.
+//
+// func ExampleCommandConfigOauth_Authenticate() {
+// authConfig := &CommandConfigOauth{
+// CommandAuthConfig: CommandAuthConfig{
+// ConfigFilePath: "/path/to/config.json",
+// ConfigProfile: "default",
+// CommandHostName: "exampleHost",
+// CommandPort: 443,
+// CommandAPIPath: "/api/v1",
+// CommandCACert: "/path/to/ca-cert.pem",
+// SkipVerify: true,
+// HttpClientTimeout: 60,
+// },
+// ClientID: "exampleClientID",
+// ClientSecret: "exampleClientSecret",
+// TokenURL: "https://example.com/oauth/token",
+// Scopes: []string{"openid", "profile", "email"},
+// Audience: "exampleAudience",
+// CACertificatePath: "/path/to/ca-cert.pem",
+// AccessToken: "exampleAccessToken",
+// }
+//
+// err := authConfig.Authenticate()
+// if err != nil {
+// fmt.Println("Authentication failed:", err)
+// } else {
+// fmt.Println("Authentication successful")
+// }
+// }
diff --git a/auth_providers/command_config.go b/auth_providers/command_config.go
index 3d5d382..45532c4 100644
--- a/auth_providers/command_config.go
+++ b/auth_providers/command_config.go
@@ -53,6 +53,7 @@ type Config struct {
Servers map[string]Server `json:"servers,omitempty" yaml:"servers,omitempty"` // Servers is a map of server configurations.
}
+// NewConfig creates a new Config configuration.
func NewConfig() *Config {
return &Config{
Servers: make(map[string]Server),
@@ -215,6 +216,7 @@ func MergeConfigFromFile(filePath string, config *Config) error {
return nil
}
+// GetAuthType returns the type of authentication to use based on the configuration params.
func (s *Server) GetAuthType() string {
if s.ClientID != "" && s.ClientSecret != "" {
s.AuthType = "oauth"
@@ -286,3 +288,55 @@ func (s *Server) GetOAuthClientConfig() (*CommandConfigOauth, error) {
}
return &oauthConfig, nil
}
+
+// Example usage of Config
+//
+// This example demonstrates how to use Config to read and write server configurations.
+//
+// func ExampleConfig_ReadWrite() {
+// config := NewConfig()
+//
+// // Add a server configuration
+// config.Servers["exampleServer"] = Server{
+// Host: "exampleHost",
+// Port: 443,
+// Username: "exampleUser",
+// Password: "examplePassword",
+// Domain: "exampleDomain",
+// ClientID: "exampleClientID",
+// ClientSecret: "exampleClientSecret",
+// OAuthTokenUrl: "https://example.com/oauth/token",
+// APIPath: "/api/v1",
+// SkipTLSVerify: true,
+// CACertPath: "/path/to/ca-cert.pem",
+// AuthType: "oauth",
+// }
+//
+// // Write the configuration to a JSON file
+// err := WriteConfigToJSON("/path/to/config.json", config)
+// if err != nil {
+// fmt.Println("Failed to write config to JSON:", err)
+// }
+//
+// // Read the configuration from a JSON file
+// readConfig, err := ReadConfigFromJSON("/path/to/config.json")
+// if err != nil {
+// fmt.Println("Failed to read config from JSON:", err)
+// } else {
+// fmt.Println("Read config from JSON:", readConfig)
+// }
+//
+// // Write the configuration to a YAML file
+// err = WriteConfigToYAML("/path/to/config.yaml", config)
+// if err != nil {
+// fmt.Println("Failed to write config to YAML:", err)
+// }
+//
+// // Read the configuration from a YAML file
+// readConfig, err = ReadConfigFromYAML("/path/to/config.yaml")
+// if err != nil {
+// fmt.Println("Failed to read config from YAML:", err)
+// } else {
+// fmt.Println("Read config from YAML:", readConfig)
+// }
+// }
diff --git a/integration-manifest.json b/integration-manifest.json
new file mode 100644
index 0000000..a01d975
--- /dev/null
+++ b/integration-manifest.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://keyfactor.github.io/integration-manifest-schema.json",
+ "integration_type": "api-client",
+ "name": "Keyfactor Auth Client - Golang",
+ "status": "production",
+ "description": "The Keyfactor Auth Client - Golang is a Go module that handles authentication and authorization for the Keyfactor API.",
+ "support_level": "kf-community",
+ "link_github": false,
+ "update_catalog": false
+}
+
diff --git a/tag.sh b/tag.sh
index c350f84..d42fdd5 100755
--- a/tag.sh
+++ b/tag.sh
@@ -1,5 +1,5 @@
-RC_VERSION=rc.18
-TAG_VERSION_1=v0.0.1-$RC_VERSION
+RC_VERSION=rc.1
+TAG_VERSION_1=v1.0.0-$RC_VERSION
git tag -d $TAG_VERSION_1 || true
git tag $TAG_VERSION_1
git push origin $TAG_VERSION_1
\ No newline at end of file
From ef80dc1ad502292448d841bbe5589afb6620c4d2 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 28 Oct 2024 11:01:16 -0700
Subject: [PATCH 46/57] fix(config): Fix invalid yaml param config
---
auth_providers/command_config.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/auth_providers/command_config.go b/auth_providers/command_config.go
index 45532c4..fee0820 100644
--- a/auth_providers/command_config.go
+++ b/auth_providers/command_config.go
@@ -37,7 +37,7 @@ type Server struct {
AuthProvider AuthProvider `json:"auth_provider,omitempty" yaml:"auth_provider,omitempty"` // AuthProvider contains the authentication provider details.
SkipTLSVerify bool `json:"skip_tls_verify,omitempty" yaml:"skip_tls_verify,omitempty"` // TLSVerify determines whether to verify the TLS certificate.
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.
+ AuthType string `json:"auth_type,omitempty" yaml:"auth_type,omitempty"` // AuthType is the type of authentication to use.
}
From a5e80f431b28a426e67671f2fe3c31d177dc3788 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 28 Oct 2024 11:17:33 -0700
Subject: [PATCH 47/57] fix(tests): `TestReadFullAuthConfigExample`
---
auth_providers/command_config_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/auth_providers/command_config_test.go b/auth_providers/command_config_test.go
index 0a43068..2aeae9f 100644
--- a/auth_providers/command_config_test.go
+++ b/auth_providers/command_config_test.go
@@ -201,7 +201,7 @@ func TestMergeConfigFromFile(t *testing.T) {
func TestReadFullAuthConfigExample(t *testing.T) {
filePath := "../lib/config/full_auth_config_example.json"
- expectedConfig := &auth_providers.Config{
+ expectedConfig := auth_providers.Config{
Servers: map[string]auth_providers.Server{
"default": {
Host: "keyfactor.command.kfdelivery.com",
@@ -253,7 +253,7 @@ func TestReadFullAuthConfigExample(t *testing.T) {
t.Fatalf("failed to unmarshal JSON: %v", err)
}
- if !compareConfigs(&config, expectedConfig) {
+ if !compareConfigs(&config, &expectedConfig) {
t.Fatalf("expected %v, got %v", expectedConfig, config)
}
}
From 31fa0340609597f2dca42bf67ea8d786557c2823 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Mon, 28 Oct 2024 12:48:15 -0700
Subject: [PATCH 48/57] chore(docs): Add docs around config file.
---
README.md | 98 ++++++++++++++++++++++-
lib/config/auth_config_schema.json | 99 +++++++++++++++++-------
lib/config/full_auth_config_example.json | 4 +-
lib/config/oauth_config_example.json | 4 +-
4 files changed, 171 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index 4265d76..c92a028 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,12 @@ Client library for authenticating to Keyfactor Command
* [Global](#global)
* [Basic Auth](#basic-auth)
* [oAuth Client Credentials](#oauth-client-credentials)
+- [Configuration File](#configuration-file)
+ * [Basic Auth](#basic-auth)
+ * [oAuth Client Credentials](#oauth-client-credentials)
+ * [Auth Providers](#auth-providers)
+ + [Azure Id](#azure-id)
+ + [Azure KeyVault](#azure-keyvault)
- [Test Environment Variables](#test-environment-variables)
@@ -48,11 +54,99 @@ Currently, only Active Directory `Basic` authentication is 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 | |
-## Test Environment Variables
+### 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 |
\ No newline at end of file
+| TEST_KEYFACTOR_KC_AUTH | Set to `true` to test Keycloak authentication | false |
+
+## Configuration File
+A JSON or YAML file can be used to store authentication configuration. A configuration file can contain references to
+multiple Keyfactor Command environments and can be referenced by a `profile` name. The `default` profile will be used
+when no profile is specified. Keyfactor tools will look for a config file located at `$HOME/.keyfactor/command_config.json`
+by default. The config file should be structured as follows:
+
+### Basic Auth
+
+#### JSON
+```json
+{
+ "servers": {
+ "default": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "username": "keyfactor",
+ "password": "password",
+ "domain": "command",
+ "api_path": "KeyfactorAPI"
+ },
+ "server2": {
+ "host": "keyfactor2.command.kfdelivery.com",
+ "username": "keyfactor2",
+ "password": "password2",
+ "domain": "command",
+ "api_path": "Keyfactor/API"
+ }
+ }
+}
+```
+
+#### YAML
+```yaml
+servers:
+ default:
+ host: keyfactor.command.kfdelivery.com
+ username: keyfactor
+ password: password
+ domain: command
+ api_path: KeyfactorAPI
+ server2:
+ host: keyfactor2.command.kfdelivery.com
+ username: keyfactor2
+ password: password2
+ domain: command
+ api_path: Keyfactor/API
+```
+
+### oAuth Client Credentials
+
+#### JSON
+```json
+{
+ "servers": {
+ "default": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "api_path": "KeyfactorAPI"
+ },
+ "server2": {
+ "host": "keyfactor.command.kfdelivery.com",
+ "token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "client_id": "client-id",
+ "client_secret": "client-secret",
+ "api_path": "KeyfactorAPI"
+ }
+ }
+}
+```
+
+#### YAML
+```yaml
+servers:
+ default:
+ host: keyfactor.command.kfdelivery.com
+ token_url: https://idp.keyfactor.command.kfdelivery.com/oauth2/token
+ client_id: client-id
+ client_secret: client-secret
+ api_path: KeyfactorAPI
+ server2:
+ host: keyfactor.command.kfdelivery.com
+ token_url: https://idp.keyfactor.command.kfdelivery.com/oauth2/token
+ client_id: client-id
+ client_secret: client-secret
+ api_path: KeyfactorAPI
+```
\ No newline at end of file
diff --git a/lib/config/auth_config_schema.json b/lib/config/auth_config_schema.json
index 7b9bfc9..2af7d48 100644
--- a/lib/config/auth_config_schema.json
+++ b/lib/config/auth_config_schema.json
@@ -1,6 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
+ "title": "Keyfactor Command API Client Configuration",
+ "description": "Configuration file schema for authenticating to the Keyfactor Command API",
"properties": {
"servers": {
"type": "object",
@@ -9,55 +11,71 @@
"type": "object",
"properties": {
"host": {
- "type": "string"
- },
- "auth_hostname": {
- "type": "string"
+ "type": "string",
+ "description": "The hostname of the Keyfactor Command API server"
},
"auth_port": {
- "type": "integer"
+ "type": "integer",
+ "description": "The port of the Keyfactor Command API server"
},
"username": {
- "type": "string"
+ "type": "string",
+ "description": "The username to authenticate with using basic auth"
},
"password": {
- "type": "string"
+ "type": "string",
+ "description": "The password to authenticate with using basic auth"
},
"client_id": {
- "type": "string"
+ "type": "string",
+ "description": "The client ID to authenticate with using OAuth2"
+ },
+ "token_url": {
+ "type": "string",
+ "description": "The token URL to authenticate with using OAuth2"
},
"client_secret": {
- "type": "string"
+ "type": "string",
+ "description": "The client secret to authenticate with using OAuth2"
},
"domain": {
- "type": "string"
+ "type": "string",
+ "description": "The Active Directory domain to authenticate with using basic auth"
},
"api_path": {
- "type": "string"
+ "type": "string",
+ "description": "The path to the Keyfactor Command API",
+ "default": "KeyfactorAPI"
},
"auth_provider": {
"type": "object",
+ "description": "The auth provider configuration",
"properties": {
"type": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "azid",
+ "akv"
+ ]
},
"profile": {
- "type": "string"
+ "type": "string",
+ "description": "The profile to use in the auth provider configuration"
},
"parameters": {
"type": "object",
+ "description": "The parameters to use in the auth provider configuration",
"properties": {
"secret_name": {
- "type": "string"
+ "type": "string",
+ "description": "The name of the secret to use in the Azure KeyVault auth provider configuration"
},
"vault_name": {
- "type": "string"
+ "type": "string",
+ "description": "The name of the vault to use in the Azure KeyVault auth provider configuration"
}
},
- "required": [
- "secret_name",
- "vault_name"
- ]
+ "required": []
}
},
"required": [
@@ -69,33 +87,58 @@
},
"oneOf": [
{
- "required": ["username", "password"],
+ "required": [
+ "username",
+ "password"
+ ],
"not": {
- "required": ["client_id", "client_secret"]
+ "required": [
+ "client_id",
+ "client_secret"
+ ]
}
},
{
- "required": ["client_id", "client_secret"],
+ "required": [
+ "client_id",
+ "client_secret",
+ "token_url"
+ ],
"not": {
- "required": ["username", "password"]
+ "required": [
+ "username",
+ "password"
+ ]
}
}
],
"if": {
- "required": ["auth_provider"]
+ "required": [
+ "auth_provider"
+ ]
},
"then": {
- "required": ["auth_provider"]
+ "required": [
+ "auth_provider"
+ ]
},
"else": {
"if": {
- "required": ["client_id", "client_secret"]
+ "required": [
+ "client_id",
+ "client_secret"
+ ]
},
"then": {
- "required": ["auth_hostname", "host"]
+ "required": [
+ "token_url",
+ "host"
+ ]
},
"else": {
- "required": ["host"]
+ "required": [
+ "host"
+ ]
}
}
}
diff --git a/lib/config/full_auth_config_example.json b/lib/config/full_auth_config_example.json
index 4bfc7eb..c2bb73c 100644
--- a/lib/config/full_auth_config_example.json
+++ b/lib/config/full_auth_config_example.json
@@ -2,7 +2,7 @@
"servers": {
"default": {
"host": "keyfactor.command.kfdelivery.com",
- "oauth_token_url": "idp.keyfactor.command.kfdelivery.com",
+ "token_url": "idp.keyfactor.command.kfdelivery.com",
"username": "keyfactor",
"password": "password",
"client_id": "client-id",
@@ -20,7 +20,7 @@
},
"server2": {
"host": "keyfactor2.command.kfdelivery.com",
- "oauth_token_url": "idp.keyfactor2.command.kfdelivery.com",
+ "token_url": "idp.keyfactor2.command.kfdelivery.com",
"username": "keyfactor2",
"password": "password2",
"client_id": "client-id2",
diff --git a/lib/config/oauth_config_example.json b/lib/config/oauth_config_example.json
index 40f097f..9c6ae86 100644
--- a/lib/config/oauth_config_example.json
+++ b/lib/config/oauth_config_example.json
@@ -2,14 +2,14 @@
"servers": {
"default": {
"host": "keyfactor.command.kfdelivery.com",
- "oauth_token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
"client_id": "client-id",
"client_secret": "client-secret",
"api_path": "KeyfactorAPI"
},
"server2": {
"host": "keyfactor.command.kfdelivery.com",
- "oauth_token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
+ "token_url": "https://idp.keyfactor.command.kfdelivery.com/oauth2/token",
"client_id": "client-id",
"client_secret": "client-secret",
"api_path": "KeyfactorAPI"
From 8b7ccc0f527fc9f10b4081d7f0e550f43d3c8ae3 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 29 Oct 2024 10:09:25 -0700
Subject: [PATCH 49/57] fix(ci): Use hosted AKS runners
---
.github/workflows/go_tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 337caf9..642623c 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -7,7 +7,7 @@ on:
jobs:
test:
name: Run tests
- runs-on: ubuntu-latest
+ runs-on: kf-auth-client-runner-set
strategy:
matrix:
environment: [ "KFC_10_5_0", "KFC_12_3_0_KC"]
From 73a9dc6aa2bf1a400840dd0659cd064e46a3de15 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Tue, 29 Oct 2024 10:35:26 -0700
Subject: [PATCH 50/57] fix(ci): Get public IP of runner
---
.github/workflows/go_tests.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 642623c..a9e2137 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -21,6 +21,9 @@ jobs:
with:
go-version: 1.22
+ - name: Get Public IP
+ run: curl -s https://api.ipify.org
+
- name: Run tests
run: |
if [ -n "${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}" ]; then
From 54983b9b8c20367b6376ae8e015f0633f0f0ba04 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Wed, 30 Oct 2024 17:22:36 -0700
Subject: [PATCH 51/57] fix(core): Support for CaCertFilePath
---
auth_providers/auth_core.go | 176 ++++++++++++++++++------------
auth_providers/auth_oauth.go | 119 ++++++++++++++------
auth_providers/auth_oauth_test.go | 83 +++++++++++++-
3 files changed, 267 insertions(+), 111 deletions(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index f8f9420..e79a8c2 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -270,70 +270,70 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
c.HttpClientTimeout = DefaultClientTimeout
}
}
- c.SetClient(nil)
+
+ if c.CommandCACert == "" {
+ // check if CommandCACert is set in environment
+ if caCert, ok := os.LookupEnv(EnvKeyfactorCACert); ok {
+ c.CommandCACert = caCert
+ } else {
+ return nil
+ }
+ }
// check for skip verify in environment
if skipVerify, ok := os.LookupEnv(EnvKeyfactorSkipVerify); ok {
c.SkipVerify = skipVerify == "true" || skipVerify == "1"
}
- if c.SkipVerify {
- c.HttpClient.Transport = &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
- return nil
- }
-
- caErr := c.updateCACerts()
- if caErr != nil {
- return caErr
- }
+ //TODO: This should be part of BuildTransport
+ //if c.SkipVerify {
+ // c.HttpClient.Transport = &http.Transport{
+ // TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ // }
+ // //return nil
+ //}
+ //
+ //caErr := c.updateCACerts()
+ //if caErr != nil {
+ // return caErr
+ //}
return nil
}
// BuildTransport creates a custom http Transport for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) BuildTransport() (*http.Transport, error) {
- output := &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tls.Config{
- Renegotiation: tls.RenegotiateOnceAsClient,
- },
- TLSHandshakeTimeout: 10 * time.Second,
- }
- if c.SkipVerify {
- output.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ var output *http.Transport
+ if c.HttpClient == nil {
+ c.SetClient(nil)
}
-
- // Load the system certs
- if c.CommandCACert != "" {
- rootCAs, pErr := x509.SystemCertPool()
- if pErr != nil {
- return nil, pErr
- }
- if rootCAs == nil {
- rootCAs = x509.NewCertPool()
- }
-
- // check if CommandCACert is a file
- if _, err := os.Stat(c.CommandCACert); err == nil {
- cert, ioErr := os.ReadFile(c.CommandCACert)
- if ioErr != nil {
- return nil, ioErr
- }
- // Append your custom cert to the pool
- if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
- return nil, fmt.Errorf("failed to append custom CA cert to pool")
- }
+ // check if c already has a transport and if it does, assign it to output else create a new transport
+ if c.HttpClient.Transport != nil {
+ if transport, ok := c.HttpClient.Transport.(*http.Transport); ok {
+ output = transport
} else {
- // Append your custom cert to the pool
- if ok := rootCAs.AppendCertsFromPEM([]byte(c.CommandCACert)); !ok {
- return nil, fmt.Errorf("failed to append custom CA cert to pool")
+ output = &http.Transport{
+ TLSClientConfig: &tls.Config{},
}
}
+ } else {
+ output = &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: &tls.Config{
+ Renegotiation: tls.RenegotiateOnceAsClient,
+ },
+ TLSHandshakeTimeout: 10 * time.Second,
+ }
+ }
+
+ if c.SkipVerify {
+ output.TLSClientConfig.InsecureSkipVerify = true
+ }
- output.TLSClientConfig.RootCAs = rootCAs
+ if c.CommandCACert != "" {
+ _ = c.updateCACerts()
}
+
return output, nil
}
@@ -343,7 +343,7 @@ func (c *CommandAuthConfig) SetClient(client *http.Client) *http.Client {
c.HttpClient = client
}
if c.HttpClient == nil {
- c.HttpClient = &http.Client{}
+ c.HttpClient = http.DefaultClient
}
return c.HttpClient
}
@@ -389,20 +389,37 @@ func (c *CommandAuthConfig) updateCACerts() error {
}
}
- // Trust the augmented cert pool in our Client
- c.HttpClient.Transport = &http.Transport{
- TLSClientConfig: &tls.Config{
- RootCAs: rootCAs,
- },
+ //check if c already has a transport and if it does, update the RootCAs else create a new transport
+ if c.HttpClient.Transport != nil {
+ if transport, ok := c.HttpClient.Transport.(*http.Transport); ok {
+ transport.TLSClientConfig.RootCAs = rootCAs
+ } else {
+ c.HttpClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: rootCAs,
+ },
+ }
+ }
+ } else {
+ c.HttpClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: rootCAs,
+ },
+ }
}
+ // Trust the augmented cert pool in our Client
+ //c.HttpClient.Transport = &http.Transport{
+ // TLSClientConfig: &tls.Config{
+ // RootCAs: rootCAs,
+ // },
+ //}
+
return nil
}
// Authenticate performs the authentication test to Keyfactor Command API and sets Command product version.
func (c *CommandAuthConfig) Authenticate() error {
- // call /Status/Endpoints API to validate credentials
- c.SetClient(nil)
//create headers for request
headers := map[string]string{
@@ -428,6 +445,7 @@ func (c *CommandAuthConfig) Authenticate() error {
if rErr != nil {
return rErr
}
+
// Set headers from the map
for key, value := range headers {
req.Header.Set(key, value)
@@ -615,29 +633,45 @@ func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, si
c.FileConfig = &server
- if !silentLoad {
+ if c.CommandHostName == "" {
c.CommandHostName = server.Host
+ }
+ if c.CommandPort <= 0 {
c.CommandPort = server.Port
+ }
+ if c.CommandAPIPath == "" {
c.CommandAPIPath = server.APIPath
+ }
+ if c.CommandCACert == "" {
c.CommandCACert = server.CACertPath
+ }
+ if c.SkipVerify {
c.SkipVerify = server.SkipTLSVerify
- } else {
- if c.CommandHostName == "" {
- c.CommandHostName = server.Host
- }
- if c.CommandPort <= 0 {
- c.CommandPort = server.Port
- }
- if c.CommandAPIPath == "" {
- c.CommandAPIPath = server.APIPath
- }
- if c.CommandCACert == "" {
- c.CommandCACert = server.CACertPath
- }
- if c.SkipVerify {
- c.SkipVerify = server.SkipTLSVerify
- }
}
+
+ //if !silentLoad {
+ // c.CommandHostName = server.Host
+ // c.CommandPort = server.Port
+ // c.CommandAPIPath = server.APIPath
+ // c.CommandCACert = server.CACertPath
+ // c.SkipVerify = server.SkipTLSVerify
+ //} else {
+ // if c.CommandHostName == "" {
+ // c.CommandHostName = server.Host
+ // }
+ // if c.CommandPort <= 0 {
+ // c.CommandPort = server.Port
+ // }
+ // if c.CommandAPIPath == "" {
+ // c.CommandAPIPath = server.APIPath
+ // }
+ // if c.CommandCACert == "" {
+ // c.CommandCACert = server.CACertPath
+ // }
+ // if c.SkipVerify {
+ // c.SkipVerify = server.SkipTLSVerify
+ // }
+ //}
return &server, nil
}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index c113614..308efe0 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -2,6 +2,7 @@ package auth_providers
import (
"context"
+ "crypto/tls"
"crypto/x509"
"fmt"
"net/http"
@@ -153,25 +154,42 @@ func (b *CommandConfigOauth) WithAccessToken(accessToken string) *CommandConfigO
return b
}
+func (b *CommandConfigOauth) WithHttpClient(httpClient *http.Client) *CommandConfigOauth {
+ b.HttpClient = httpClient
+ return b
+}
+
// GetHttpClient returns an HTTP client for oAuth authentication.
func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
cErr := b.ValidateAuthConfig()
+ var client http.Client
+ if b.CommandAuthConfig.HttpClient != nil {
+ client = *b.CommandAuthConfig.HttpClient
+ }
if cErr != nil {
return nil, cErr
}
+ if client.Transport == nil {
+ transport, tErr := b.BuildTransport()
+ if tErr != nil {
+ return nil, tErr
+ }
+ client.Transport = transport
+ }
+
if b.AccessToken != "" {
- return &http.Client{
- Transport: &oauth2.Transport{
- Base: http.DefaultTransport,
- Source: oauth2.StaticTokenSource(
- &oauth2.Token{
- AccessToken: b.AccessToken,
- TokenType: DefaultTokenPrefix,
- },
- ),
- },
- }, nil
+ baseTransport := cloneHTTPTransport(client.Transport.(*http.Transport))
+ client.Transport = &oauth2.Transport{
+ Base: baseTransport,
+ Source: oauth2.StaticTokenSource(
+ &oauth2.Token{
+ AccessToken: b.AccessToken,
+ TokenType: DefaultTokenPrefix,
+ },
+ ),
+ }
+ return &client, nil
}
config := &clientcredentials.Config{
@@ -191,21 +209,17 @@ func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
}
}
- transport, tErr := b.BuildTransport()
- if tErr != nil {
- return nil, tErr
- }
+ ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
- tokenSource := config.TokenSource(context.Background())
- oauthTransport := &oauth2.Transport{
- Base: transport,
+ tokenSource := config.TokenSource(ctx)
+ baseTransport := cloneHTTPTransport(client.Transport.(*http.Transport))
+ oauthTransport := oauth2.Transport{
+ Base: baseTransport,
Source: tokenSource,
}
+ client.Transport = &oauthTransport
- return &http.Client{
- Transport: oauthTransport,
- }, nil
-
+ return &client, nil
}
// Build creates an OAuth authenticator.
@@ -352,24 +366,15 @@ func (b *CommandConfigOauth) Authenticate() error {
}
// create oauth Client
- oauthy, err := NewOAuthAuthenticatorBuilder().
- WithClientId(b.ClientID).
- WithClientSecret(b.ClientSecret).
- WithTokenUrl(b.TokenURL).
- WithAccessToken(b.AccessToken).
- Build()
+ oauthy, err := b.GetHttpClient()
if err != nil {
return err
+ } else if oauthy == nil {
+ return fmt.Errorf("unable to create http client")
}
- if oauthy != nil {
- oClient, oerr := oauthy.GetHttpClient()
- if oerr != nil {
- return oerr
- }
- b.SetClient(oClient)
- }
+ b.SetClient(oauthy)
aErr := b.CommandAuthConfig.Authenticate()
if aErr != nil {
@@ -428,3 +433,47 @@ func (b *CommandConfigOauth) GetServerConfig() *Server {
// fmt.Println("Authentication successful")
// }
// }
+
+func cloneHTTPTransport(original *http.Transport) *http.Transport {
+ if original == nil {
+ return nil
+ }
+
+ return &http.Transport{
+ Proxy: original.Proxy,
+ DialContext: original.DialContext,
+ ForceAttemptHTTP2: original.ForceAttemptHTTP2,
+ MaxIdleConns: original.MaxIdleConns,
+ IdleConnTimeout: original.IdleConnTimeout,
+ TLSHandshakeTimeout: original.TLSHandshakeTimeout,
+ ExpectContinueTimeout: original.ExpectContinueTimeout,
+ ResponseHeaderTimeout: original.ResponseHeaderTimeout,
+ TLSClientConfig: cloneTLSConfig(original.TLSClientConfig),
+ DialTLSContext: original.DialTLSContext,
+ DisableKeepAlives: original.DisableKeepAlives,
+ DisableCompression: original.DisableCompression,
+ MaxIdleConnsPerHost: original.MaxIdleConnsPerHost,
+ MaxConnsPerHost: original.MaxConnsPerHost,
+ WriteBufferSize: original.WriteBufferSize,
+ ReadBufferSize: original.ReadBufferSize,
+ }
+}
+
+func cloneTLSConfig(original *tls.Config) *tls.Config {
+ if original == nil {
+ return nil
+ }
+
+ return &tls.Config{
+ InsecureSkipVerify: original.InsecureSkipVerify,
+ MinVersion: original.MinVersion,
+ MaxVersion: original.MaxVersion,
+ CipherSuites: original.CipherSuites,
+ PreferServerCipherSuites: original.PreferServerCipherSuites,
+ NextProtos: original.NextProtos,
+ ServerName: original.ServerName,
+ ClientAuth: original.ClientAuth,
+ RootCAs: original.RootCAs,
+ // Deep copy the rest of the TLS fields as needed
+ }
+}
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 0ee1667..5e5fb87 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -107,7 +107,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
t.FailNow()
}
- // Delete the config file
+ //Delete the config file
t.Logf("Deleting config file: %s", configFilePath)
os.Remove(configFilePath)
defer func() {
@@ -118,9 +118,15 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
}()
+ //os.Setenv(auth_providers.EnvKeyfactorConfigFile, configFilePath)
+ //os.Setenv(auth_providers.EnvKeyfactorAuthProfile, "oauth")
+ //os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
t.Log("Testing oAuth with Environmental variables")
noParamsConfig := &auth_providers.CommandConfigOauth{}
+ noParamsConfig.
+ WithSkipVerify(true).
+ WithCommandCACert("lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem")
authOauthTest(t, "with complete Environmental variables", false, noParamsConfig)
t.Log("Testing oAuth with invalid config file path")
@@ -131,6 +137,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
// Environment variables are not set
t.Log("Unsetting environment variables")
+ //keyfactorEnvVars := exportEnvVarsWithPrefix("KEYFACTOR_")
clientID, clientSecret, tokenURL := exportOAuthEnvVariables()
unsetOAuthEnvVariables()
defer func() {
@@ -168,6 +175,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
ClientSecret: clientSecret,
TokenURL: tokenURL,
}
+ fullParamsConfig.WithSkipVerify(true)
authOauthTest(t, "w/ full params variables", false, fullParamsConfig)
t.Log("Testing auth with w/ full params & invalid pass")
@@ -199,11 +207,51 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
t.Errorf("unable to write auth config to file %s: %v", configFilePath, fErr)
}
- t.Log("Testing oAuth with valid implicit config file")
- wConfigFile := &auth_providers.CommandConfigOauth{}
- wConfigFile.WithConfigProfile("oauth")
- authOauthTest(t, "with valid implicit config file", false, wConfigFile)
+ //unsetOAuthEnvVariables()
+ t.Log("Testing oAuth with valid implicit config file profile param, caCert, and skiptls")
+ wCaCertConfigFile := &auth_providers.CommandConfigOauth{}
+ wCaCertConfigFile.
+ WithConfigProfile("oauth").
+ WithCommandCACert("lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem").
+ WithSkipVerify(true)
+ authOauthTest(
+ t, "oAuth with valid implicit config file profile param, caCert, and skiptls", false,
+ wCaCertConfigFile,
+ )
+
+ t.Log("Testing oAuth with skiptls param and valid implicit config file")
+ skipTLSConfigFileP := &auth_providers.CommandConfigOauth{}
+ skipTLSConfigFileP.
+ WithConfigProfile("oauth").
+ WithSkipVerify(true)
+ authOauthTest(t, "with skiptls param and valid implicit config file", false, skipTLSConfigFileP)
+
+ t.Log("Testing oAuth with valid implicit config file skiptls config param")
+ skipTLSConfigFileC := &auth_providers.CommandConfigOauth{}
+ skipTLSConfigFileC.
+ WithConfigProfile("oauth-skiptls")
+ authOauthTest(t, "with oAuth with valid implicit config file skiptls config param", false, skipTLSConfigFileC)
+
+ t.Log("Testing oAuth with valid implicit config file skiptls env")
+ os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
+ skipTLSConfigFileE := &auth_providers.CommandConfigOauth{}
+ skipTLSConfigFileE.
+ WithConfigProfile("oauth")
+ authOauthTest(t, "oAuth with valid implicit config file skiptls env", false, skipTLSConfigFileE)
+ os.Unsetenv(auth_providers.EnvKeyfactorSkipVerify)
+
+ t.Log("Testing oAuth with valid implicit config file https fail")
+ httpsFailConfigFile := &auth_providers.CommandConfigOauth{}
+ httpsFailConfigFile.
+ WithConfigProfile("oauth")
+ httpsFailConfigFileExpected := []string{"tls: failed to verify certificate", "certificate is not trusted"}
+ authOauthTest(
+ t, "oAuth with valid implicit config file https fail", true, httpsFailConfigFile,
+ httpsFailConfigFileExpected...,
+ )
+
+ os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
t.Log("Testing oAuth with invalid profile implicit config file")
invProfile := &auth_providers.CommandConfigOauth{}
invProfile.WithConfigProfile("invalid-profile")
@@ -287,6 +335,21 @@ func setOAuthEnvVariables(client_id, client_secret, token_url string) {
os.Setenv(auth_providers.EnvKeyfactorAuthTokenURL, token_url)
}
+func exportEnvVarsWithPrefix(prefix string) map[string]string {
+ result := make(map[string]string)
+ for _, env := range os.Environ() {
+ // Each environment variable is in the format "KEY=VALUE"
+ pair := strings.SplitN(env, "=", 2)
+ key := pair[0]
+ value := pair[1]
+
+ if strings.HasPrefix(key, prefix) {
+ result[key] = value
+ }
+ }
+ return result
+}
+
// exportOAuthEnvVariables sets the oAuth environment variables
func exportOAuthEnvVariables() (string, string, string) {
client_id := os.Getenv(auth_providers.EnvKeyfactorClientID)
@@ -300,4 +363,14 @@ func unsetOAuthEnvVariables() {
os.Unsetenv(auth_providers.EnvKeyfactorClientID)
os.Unsetenv(auth_providers.EnvKeyfactorClientSecret)
os.Unsetenv(auth_providers.EnvKeyfactorAuthTokenURL)
+ os.Unsetenv(auth_providers.EnvKeyfactorSkipVerify)
+ os.Unsetenv(auth_providers.EnvKeyfactorConfigFile)
+ os.Unsetenv(auth_providers.EnvKeyfactorAuthProfile)
+ os.Unsetenv(auth_providers.EnvKeyfactorCACert)
+ os.Unsetenv(auth_providers.EnvAuthCACert)
+ //os.Unsetenv(auth_providers.EnvKeyfactorHostName)
+ //os.Unsetenv(auth_providers.EnvKeyfactorUsername)
+ //os.Unsetenv(auth_providers.EnvKeyfactorPassword)
+ //os.Unsetenv(auth_providers.EnvKeyfactorDomain)
+
}
From 1fb370e1fae37f56057379ad98d79b91c6bd00f7 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 11:46:35 -0700
Subject: [PATCH 52/57] fix(oauth): OAuth clients not to inherit DefaultClient
config
---
auth_providers/auth_core.go | 98 +++++++++++---------
auth_providers/auth_oauth.go | 117 ++++++------------------
auth_providers/auth_oauth_test.go | 72 +++++++++++++--
main.go | 143 +++++++++++++++++++++++++++++-
4 files changed, 287 insertions(+), 143 deletions(-)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index e79a8c2..390646b 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -141,6 +141,7 @@ type CommandAuthConfig struct {
// HttpClient is the http Client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
+ //DefaultHttpClient *http.Client
}
// cleanHostName cleans the hostname for authentication to Keyfactor Command API.
@@ -275,8 +276,6 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
// check if CommandCACert is set in environment
if caCert, ok := os.LookupEnv(EnvKeyfactorCACert); ok {
c.CommandCACert = caCert
- } else {
- return nil
}
}
@@ -284,46 +283,17 @@ func (c *CommandAuthConfig) ValidateAuthConfig() error {
if skipVerify, ok := os.LookupEnv(EnvKeyfactorSkipVerify); ok {
c.SkipVerify = skipVerify == "true" || skipVerify == "1"
}
-
- //TODO: This should be part of BuildTransport
- //if c.SkipVerify {
- // c.HttpClient.Transport = &http.Transport{
- // TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- // }
- // //return nil
- //}
- //
- //caErr := c.updateCACerts()
- //if caErr != nil {
- // return caErr
- //}
-
return nil
}
// BuildTransport creates a custom http Transport for authentication to Keyfactor Command API.
func (c *CommandAuthConfig) BuildTransport() (*http.Transport, error) {
- var output *http.Transport
- if c.HttpClient == nil {
- c.SetClient(nil)
- }
- // check if c already has a transport and if it does, assign it to output else create a new transport
- if c.HttpClient.Transport != nil {
- if transport, ok := c.HttpClient.Transport.(*http.Transport); ok {
- output = transport
- } else {
- output = &http.Transport{
- TLSClientConfig: &tls.Config{},
- }
- }
- } else {
- output = &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tls.Config{
- Renegotiation: tls.RenegotiateOnceAsClient,
- },
- TLSHandshakeTimeout: 10 * time.Second,
- }
+ output := http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: &tls.Config{
+ Renegotiation: tls.RenegotiateOnceAsClient,
+ },
+ TLSHandshakeTimeout: 10 * time.Second,
}
if c.SkipVerify {
@@ -331,10 +301,28 @@ func (c *CommandAuthConfig) BuildTransport() (*http.Transport, error) {
}
if c.CommandCACert != "" {
- _ = c.updateCACerts()
+ if _, err := os.Stat(c.CommandCACert); err == nil {
+ cert, ioErr := os.ReadFile(c.CommandCACert)
+ if ioErr != nil {
+ return &output, ioErr
+ }
+ // check if output.TLSClientConfig.RootCAs is nil
+ if output.TLSClientConfig.RootCAs == nil {
+ output.TLSClientConfig.RootCAs = x509.NewCertPool()
+ }
+ // Append your custom cert to the pool
+ if ok := output.TLSClientConfig.RootCAs.AppendCertsFromPEM(cert); !ok {
+ return &output, fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ } else {
+ // Append your custom cert to the pool
+ if ok := output.TLSClientConfig.RootCAs.AppendCertsFromPEM([]byte(c.CommandCACert)); !ok {
+ return &output, fmt.Errorf("failed to append custom CA cert to pool")
+ }
+ }
}
- return output, nil
+ return &output, nil
}
// SetClient sets the http Client for authentication to Keyfactor Command API.
@@ -343,8 +331,34 @@ func (c *CommandAuthConfig) SetClient(client *http.Client) *http.Client {
c.HttpClient = client
}
if c.HttpClient == nil {
- c.HttpClient = http.DefaultClient
+ //// Copy the default transport and apply the custom TLS config
+ //defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
+ ////defaultTransport.TLSClientConfig = tlsConfig
+ //c.HttpClient = &http.Client{Transport: defaultTransport}
+ defaultTimeout := time.Duration(c.HttpClientTimeout) * time.Second
+ c.HttpClient = &http.Client{
+ Transport: &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: &tls.Config{
+ Renegotiation: tls.RenegotiateOnceAsClient,
+ },
+ TLSHandshakeTimeout: defaultTimeout,
+ DisableKeepAlives: false,
+ DisableCompression: false,
+ MaxIdleConns: 10,
+ MaxIdleConnsPerHost: 10,
+ MaxConnsPerHost: 10,
+ IdleConnTimeout: defaultTimeout,
+ ResponseHeaderTimeout: defaultTimeout,
+ ExpectContinueTimeout: defaultTimeout,
+ MaxResponseHeaderBytes: 0,
+ WriteBufferSize: 0,
+ ReadBufferSize: 0,
+ ForceAttemptHTTP2: false,
+ },
+ }
}
+
return c.HttpClient
}
@@ -356,6 +370,7 @@ func (c *CommandAuthConfig) updateCACerts() error {
if caCert, ok := os.LookupEnv(EnvKeyfactorCACert); ok {
c.CommandCACert = caCert
} else {
+ // nothing to do
return nil
}
}
@@ -452,7 +467,6 @@ func (c *CommandAuthConfig) Authenticate() error {
}
c.HttpClient.Timeout = time.Duration(c.HttpClientTimeout) * time.Second
-
cResp, cErr := c.HttpClient.Do(req)
if cErr != nil {
return cErr
@@ -645,7 +659,7 @@ func (c *CommandAuthConfig) LoadConfig(profile string, configFilePath string, si
if c.CommandCACert == "" {
c.CommandCACert = server.CACertPath
}
- if c.SkipVerify {
+ if !c.SkipVerify {
c.SkipVerify = server.SkipTLSVerify
}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index 308efe0..8a8c940 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -2,7 +2,6 @@ package auth_providers
import (
"context"
- "crypto/tls"
"crypto/x509"
"fmt"
"net/http"
@@ -51,6 +50,11 @@ type OAuthAuthenticator struct {
Client *http.Client
}
+type oauth2Transport struct {
+ base http.RoundTripper
+ src oauth2.TokenSource
+}
+
// GetHttpClient returns the http client
func (a *OAuthAuthenticator) GetHttpClient() (*http.Client, error) {
return a.Client, nil
@@ -162,24 +166,17 @@ func (b *CommandConfigOauth) WithHttpClient(httpClient *http.Client) *CommandCon
// GetHttpClient returns an HTTP client for oAuth authentication.
func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
cErr := b.ValidateAuthConfig()
- var client http.Client
- if b.CommandAuthConfig.HttpClient != nil {
- client = *b.CommandAuthConfig.HttpClient
- }
if cErr != nil {
return nil, cErr
}
- if client.Transport == nil {
- transport, tErr := b.BuildTransport()
- if tErr != nil {
- return nil, tErr
- }
- client.Transport = transport
+ var client http.Client
+ baseTransport, tErr := b.BuildTransport()
+ if tErr != nil {
+ return nil, tErr
}
if b.AccessToken != "" {
- baseTransport := cloneHTTPTransport(client.Transport.(*http.Transport))
client.Transport = &oauth2.Transport{
Base: baseTransport,
Source: oauth2.StaticTokenSource(
@@ -209,15 +206,15 @@ func (b *CommandConfigOauth) GetHttpClient() (*http.Client, error) {
}
}
- ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
-
+ ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Transport: baseTransport})
tokenSource := config.TokenSource(ctx)
- baseTransport := cloneHTTPTransport(client.Transport.(*http.Transport))
- oauthTransport := oauth2.Transport{
- Base: baseTransport,
- Source: tokenSource,
+
+ client = http.Client{
+ Transport: &oauth2Transport{
+ base: baseTransport,
+ src: tokenSource,
+ },
}
- client.Transport = &oauthTransport
return &client, nil
}
@@ -375,6 +372,7 @@ func (b *CommandConfigOauth) Authenticate() error {
}
b.SetClient(oauthy)
+ //b.DefaultHttpClient = oauthy
aErr := b.CommandAuthConfig.Authenticate()
if aErr != nil {
@@ -401,79 +399,16 @@ func (b *CommandConfigOauth) GetServerConfig() *Server {
return &server
}
-// Example usage of CommandConfigOauth
-//
-// This example demonstrates how to use CommandConfigOauth to authenticate to the Keyfactor Command API using OAuth2.
-//
-// func ExampleCommandConfigOauth_Authenticate() {
-// authConfig := &CommandConfigOauth{
-// CommandAuthConfig: CommandAuthConfig{
-// ConfigFilePath: "/path/to/config.json",
-// ConfigProfile: "default",
-// CommandHostName: "exampleHost",
-// CommandPort: 443,
-// CommandAPIPath: "/api/v1",
-// CommandCACert: "/path/to/ca-cert.pem",
-// SkipVerify: true,
-// HttpClientTimeout: 60,
-// },
-// ClientID: "exampleClientID",
-// ClientSecret: "exampleClientSecret",
-// TokenURL: "https://example.com/oauth/token",
-// Scopes: []string{"openid", "profile", "email"},
-// Audience: "exampleAudience",
-// CACertificatePath: "/path/to/ca-cert.pem",
-// AccessToken: "exampleAccessToken",
-// }
-//
-// err := authConfig.Authenticate()
-// if err != nil {
-// fmt.Println("Authentication failed:", err)
-// } else {
-// fmt.Println("Authentication successful")
-// }
-// }
-
-func cloneHTTPTransport(original *http.Transport) *http.Transport {
- if original == nil {
- return nil
+// RoundTrip executes a single HTTP transaction, adding the OAuth2 token to the request
+func (t *oauth2Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ token, err := t.src.Token()
+ if err != nil {
+ return nil, fmt.Errorf("failed to retrieve OAuth token: %w", err)
}
- return &http.Transport{
- Proxy: original.Proxy,
- DialContext: original.DialContext,
- ForceAttemptHTTP2: original.ForceAttemptHTTP2,
- MaxIdleConns: original.MaxIdleConns,
- IdleConnTimeout: original.IdleConnTimeout,
- TLSHandshakeTimeout: original.TLSHandshakeTimeout,
- ExpectContinueTimeout: original.ExpectContinueTimeout,
- ResponseHeaderTimeout: original.ResponseHeaderTimeout,
- TLSClientConfig: cloneTLSConfig(original.TLSClientConfig),
- DialTLSContext: original.DialTLSContext,
- DisableKeepAlives: original.DisableKeepAlives,
- DisableCompression: original.DisableCompression,
- MaxIdleConnsPerHost: original.MaxIdleConnsPerHost,
- MaxConnsPerHost: original.MaxConnsPerHost,
- WriteBufferSize: original.WriteBufferSize,
- ReadBufferSize: original.ReadBufferSize,
- }
-}
+ // Clone the request to avoid mutating the original
+ reqCopy := req.Clone(req.Context())
+ token.SetAuthHeader(reqCopy)
-func cloneTLSConfig(original *tls.Config) *tls.Config {
- if original == nil {
- return nil
- }
-
- return &tls.Config{
- InsecureSkipVerify: original.InsecureSkipVerify,
- MinVersion: original.MinVersion,
- MaxVersion: original.MaxVersion,
- CipherSuites: original.CipherSuites,
- PreferServerCipherSuites: original.PreferServerCipherSuites,
- NextProtos: original.NextProtos,
- ServerName: original.ServerName,
- ClientAuth: original.ClientAuth,
- RootCAs: original.RootCAs,
- // Deep copy the rest of the TLS fields as needed
- }
+ return t.base.RoundTrip(reqCopy)
}
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 5e5fb87..c2c68ad 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -120,14 +120,62 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
}()
//os.Setenv(auth_providers.EnvKeyfactorConfigFile, configFilePath)
//os.Setenv(auth_providers.EnvKeyfactorAuthProfile, "oauth")
- //os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
+ os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
+ os.Setenv(auth_providers.EnvKeyfactorCACert, "lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem")
- t.Log("Testing oAuth with Environmental variables")
+ // Begin test case
+ noParamsTestName := fmt.Sprintf(
+ "w/ complete ENV variables & %s,%s", auth_providers.EnvKeyfactorCACert,
+ auth_providers.EnvKeyfactorSkipVerify,
+ )
+ t.Log(fmt.Sprintf("Testing %s", noParamsTestName))
noParamsConfig := &auth_providers.CommandConfigOauth{}
- noParamsConfig.
- WithSkipVerify(true).
- WithCommandCACert("lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem")
- authOauthTest(t, "with complete Environmental variables", false, noParamsConfig)
+ authOauthTest(
+ t, noParamsTestName, false, noParamsConfig,
+ )
+ t.Logf("Unsetting environment variable %s", auth_providers.EnvKeyfactorCACert)
+ os.Unsetenv(auth_providers.EnvKeyfactorCACert)
+ t.Logf("Unsetting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
+ os.Unsetenv(auth_providers.EnvKeyfactorSkipVerify)
+ // end test case
+
+ // Begin test case
+ noParamsTestName = fmt.Sprintf(
+ "w/ complete ENV variables & %s", auth_providers.EnvKeyfactorCACert,
+ )
+ t.Log(fmt.Sprintf("Testing %s", noParamsTestName))
+ t.Logf("Setting environment variable %s", auth_providers.EnvKeyfactorCACert)
+ os.Setenv(auth_providers.EnvKeyfactorCACert, "lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem")
+ noParamsConfig = &auth_providers.CommandConfigOauth{}
+ authOauthTest(t, noParamsTestName, false, noParamsConfig)
+ t.Logf("Unsetting environment variable %s", auth_providers.EnvKeyfactorCACert)
+ os.Unsetenv(auth_providers.EnvKeyfactorCACert)
+ // end test case
+
+ // Begin test case
+ noParamsTestName = fmt.Sprintf(
+ "w/ complete ENV variables & %s", auth_providers.EnvKeyfactorSkipVerify,
+ )
+ t.Log(fmt.Sprintf("Testing %s", noParamsTestName))
+ t.Logf("Setting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
+ os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
+ noParamsConfig = &auth_providers.CommandConfigOauth{}
+ authOauthTest(t, noParamsTestName, false, noParamsConfig)
+ t.Logf("Unsetting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
+ os.Unsetenv(auth_providers.EnvKeyfactorSkipVerify)
+ // end test case
+
+ // Begin test case
+ noParamsConfig = &auth_providers.CommandConfigOauth{}
+ httpsFailEnvExpected := []string{"tls: failed to verify certificate", "certificate is not trusted"}
+ authOauthTest(
+ t,
+ fmt.Sprintf("w/o env %s", auth_providers.EnvKeyfactorCACert),
+ true,
+ noParamsConfig,
+ httpsFailEnvExpected...,
+ )
+ // end test case
t.Log("Testing oAuth with invalid config file path")
invFilePath := &auth_providers.CommandConfigOauth{}
@@ -184,6 +232,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
ClientSecret: "invalid-client-secret",
TokenURL: tokenURL,
}
+ fullParamsInvalidPassConfig.WithSkipVerify(true)
invalidCredsExpectedError := []string{
"oauth2", "unauthorized_client", "Invalid client or Invalid client credentials",
}
@@ -234,11 +283,13 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
authOauthTest(t, "with oAuth with valid implicit config file skiptls config param", false, skipTLSConfigFileC)
t.Log("Testing oAuth with valid implicit config file skiptls env")
+ t.Logf("Setting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
skipTLSConfigFileE := &auth_providers.CommandConfigOauth{}
skipTLSConfigFileE.
WithConfigProfile("oauth")
authOauthTest(t, "oAuth with valid implicit config file skiptls env", false, skipTLSConfigFileE)
+ t.Logf("Unsetting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
os.Unsetenv(auth_providers.EnvKeyfactorSkipVerify)
t.Log("Testing oAuth with valid implicit config file https fail")
@@ -251,7 +302,6 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
httpsFailConfigFileExpected...,
)
- os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true")
t.Log("Testing oAuth with invalid profile implicit config file")
invProfile := &auth_providers.CommandConfigOauth{}
invProfile.WithConfigProfile("invalid-profile")
@@ -260,12 +310,16 @@ 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")
+ invProfileCreds.
+ 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")
+ invCmdHost.
+ 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/main.go b/main.go
index f1615e2..96f5009 100644
--- a/main.go
+++ b/main.go
@@ -17,11 +17,152 @@ package main
import (
"fmt"
- "github.com/Keyfactor/keyfactor-auth-client-go/pkg" // Correct
+ "github.com/Keyfactor/keyfactor-auth-client-go/pkg"
)
func main() {
fmt.Println("Version:", pkg.Version) // print the package version
fmt.Println("Build:", pkg.BuildTime) // print the package build
fmt.Println("Commit:", pkg.CommitHash) // print the package commit
+ //testClients()
}
+
+//func testClients() {
+// // URL to test against
+// url := os.Getenv("KEYFACTOR_AUTH_TOKEN_URL")
+// caCertPath := os.Getenv("KEYFACTOR_CA_CERT")
+//
+// // Load the custom root CA certificate
+// caCert, err := os.ReadFile(caCertPath)
+// if err != nil {
+// log.Fatalf("Failed to read root CA certificate: %v", err)
+// }
+//
+// // Create a certificate pool and add the custom root CA
+// caCertPool := x509.NewCertPool()
+// if !caCertPool.AppendCertsFromPEM(caCert) {
+// log.Fatalf("Failed to append root CA certificate to pool")
+// }
+//
+// // OAuth2 client credentials configuration
+// clientId := os.Getenv("KEYFACTOR_AUTH_CLIENT_ID")
+// clientSecret := os.Getenv("KEYFACTOR_AUTH_CLIENT_SECRET")
+// oauthConfig := &clientcredentials.Config{
+// ClientID: clientId,
+// ClientSecret: clientSecret,
+// TokenURL: url,
+// }
+//
+// // Transport with default TLS verification (InsecureSkipVerify = false)
+// transportDefaultTLS := &http.Transport{
+// TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
+// }
+//
+// // Transport with TLS verification skipped (InsecureSkipVerify = true)
+// transportInsecureTLS := &http.Transport{
+// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+// }
+//
+// // Transport with custom CA verification
+// transportCustomRootCA := &http.Transport{
+// TLSClientConfig: &tls.Config{
+// RootCAs: caCertPool, // Custom root CA pool
+// InsecureSkipVerify: false, // Enforce TLS verification
+// },
+// }
+//
+// // OAuth2 Token Sources
+// tokenSourceDefaultTLS := oauthConfig.TokenSource(context.Background())
+//
+// ctxInsecure := context.WithValue(
+// context.Background(),
+// oauth2.HTTPClient,
+// &http.Client{Transport: transportInsecureTLS},
+// )
+// tokenSourceInsecureTLS := oauthConfig.TokenSource(ctxInsecure)
+//
+// ctxCustomCA := context.WithValue(
+// context.Background(),
+// oauth2.HTTPClient,
+// &http.Client{Transport: transportCustomRootCA},
+// )
+// tokenSourceCustomRootCA := oauthConfig.TokenSource(ctxCustomCA)
+//
+// // OAuth2 clients with different transports
+// oauthClientDefaultTLS := &http.Client{
+// Transport: &oauth2Transport{
+// base: transportDefaultTLS,
+// src: tokenSourceDefaultTLS,
+// },
+// }
+//
+// oauthClientInsecureTLS := &http.Client{
+// Transport: &oauth2Transport{
+// base: transportInsecureTLS,
+// src: tokenSourceInsecureTLS,
+// },
+// }
+//
+// oauthClientCustomRootCA := &http.Client{
+// Transport: &oauth2Transport{
+// base: transportCustomRootCA,
+// src: tokenSourceCustomRootCA,
+// },
+// }
+//
+// // Prepare the GET request
+// req, err := http.NewRequest("GET", url, nil)
+// if err != nil {
+// log.Fatalf("Failed to create request: %v", err)
+// }
+//
+// // Test 1: OAuth2 client with default TLS verification (expected to fail if certificate is invalid)
+// fmt.Println("Testing OAuth2 client with default TLS verification...")
+// resp1, err1 := oauthClientDefaultTLS.Do(req)
+// if err1 != nil {
+// log.Printf("OAuth2 client with default TLS failed as expected: %v\n", err1)
+// } else {
+// fmt.Printf("OAuth2 client with default TLS succeeded: %s\n", resp1.Status)
+// resp1.Body.Close()
+// }
+//
+// // Test 2: OAuth2 client with skipped TLS verification (should succeed)
+// fmt.Println("\nTesting OAuth2 client with skipped TLS verification...")
+// resp2, err2 := oauthClientInsecureTLS.Do(req)
+// if err2 != nil {
+// log.Fatalf("OAuth2 client with skipped TLS failed: %v\n", err2)
+// } else {
+// fmt.Printf("OAuth2 client with skipped TLS succeeded: %s\n", resp2.Status)
+// resp2.Body.Close()
+// }
+//
+// // Test 3: OAuth2 client with custom root CA (should succeed if the CA is valid)
+// fmt.Println("\nTesting OAuth2 client with custom root CA verification...")
+// resp3, err3 := oauthClientCustomRootCA.Do(req)
+// if err3 != nil {
+// log.Fatalf("OAuth2 client with custom root CA failed: %v\n", err3)
+// } else {
+// fmt.Printf("OAuth2 client with custom root CA succeeded: %s\n", resp3.Status)
+// resp3.Body.Close()
+// }
+//}
+//
+//// oauth2Transport is a custom RoundTripper that injects the OAuth2 token into requests
+//type oauth2Transport struct {
+// base http.RoundTripper
+// src oauth2.TokenSource
+//}
+//
+//// RoundTrip executes a single HTTP transaction, adding the OAuth2 token to the request
+//func (t *oauth2Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+// token, err := t.src.Token()
+// if err != nil {
+// return nil, fmt.Errorf("failed to retrieve OAuth token: %w", err)
+// }
+//
+// // Clone the request to avoid mutating the original
+// reqCopy := req.Clone(req.Context())
+// token.SetAuthHeader(reqCopy)
+//
+// return t.base.RoundTrip(reqCopy)
+//}
From c91587315b62ef5a877b92024c37c59274a6ccd8 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 11:53:14 -0700
Subject: [PATCH 53/57] fix(core): If client isn't set when `Authenticate()` is
called, created an empty client.
---
auth_providers/auth_core.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 390646b..97d1295 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -436,6 +436,9 @@ func (c *CommandAuthConfig) updateCACerts() error {
// Authenticate performs the authentication test to Keyfactor Command API and sets Command product version.
func (c *CommandAuthConfig) Authenticate() error {
+ if c.HttpClient == nil {
+ c.SetClient(nil)
+ }
//create headers for request
headers := map[string]string{
"Content-Type": "application/json",
From 0d8cee6010b2f2691999440ecdc3cfddba36e16f Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 11:56:09 -0700
Subject: [PATCH 54/57] fix(tests): Add oidc lab cert
---
...nt-oidc-lab.eastus2.cloudapp.azure.com.pem | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem
diff --git a/lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem b/lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem
new file mode 100644
index 0000000..6ef8014
--- /dev/null
+++ b/lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIENTCCAp2gAwIBAgIUXiYBpLlOyvIAuP9iZ8NmIfftHq0wDQYJKoZIhvcNAQEL
+BQAwMDEXMBUGCgmSJomT8ixkAQEMBzI0Mjc4MzcxFTATBgNVBAMMDE1hbmFnZW1l
+bnRDQTAeFw0yNDEwMTYxNjUxMzdaFw0yNTExMTcxNjQyMzVaMDIxMDAuBgNVBAMM
+J2ludC1vaWRjLWxhYi5lYXN0dXMyLmNsb3VkYXBwLmF6dXJlLmNvbTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO9bxuPyHOytYPN7KIz2IBuuVuKMh7YU
+yuZHJ2CiFfOOOnZlojaKjsp5j8E50h4tc5aPBWDZ0Cma7Ty3pdsssRsa78JwQ8cG
+oOUumrRYySzAET6OJA8Pn00t+lxLxBf4St3buobhQTEMesCsKdCSWbAydiIgkKA8
+E02zPKzXJU6yMmV/JnMPqEQHUBC8yb4NmfXsMplkVYkdhqESL+0xYYJLqireDuR3
+LfDdvZkh0XovgdN9zJpf10KP2os2tOzaa35dXVCBsl7y/IPaDt+zYb0OX88TGOPl
+1AysYO35lS+6CGiENn2ACTZSmvqOcwjOLB5dJd95b1lnUwsJgmMcSesCAwEAAaOB
+xDCBwTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFOOYYJwB1O/MQlfbORHp1YqM
+Uy0MMEwGA1UdEQRFMEOCJ2ludC1vaWRjLWxhYi5lYXN0dXMyLmNsb3VkYXBwLmF6
+dXJlLmNvbYIMaW50LW9pZGMtbGFihwQUAYhEhwQKCgAEMBMGA1UdJQQMMAoGCCsG
+AQUFBwMBMB0GA1UdDgQWBBTBsBvKKfjz/P14o0c3V/fLm/hXmDAOBgNVHQ8BAf8E
+BAMCBaAwDQYJKoZIhvcNAQELBQADggGBAEizjLNGgjGNML6L4On1EhcDAtGIH6SY
+mkiP/mcMXcxESx/wAZAQO04ERbVX3mnewdmq1+TsnbfxEtetAobaxlRRej6tzFQp
+BBqGqz7uK8O3CpzBZzuBtR2d+GZw6+amBbxGyZvsIW0f2RQqaHp+FgBgJjZc3t+/
+UmaRoS2h26VBXuv40bEgErih/cI80xnJtRKtxS1/hJNvzeDMobgZ0KkZz1j/tHbi
+DzaX8CDCg0fhMH5BuSleG9MSYcW36TL30pv/h92kN8EldQgpWwMW0C3aTZBEpiCw
+NGeiUmbciia//rOi4Rin4uHCe7sllb10MTaLcy251S9vAmmHvSBj33iLlCms2WS7
+02BwsPPGilydfjKqekoC6M1AwxiSDp8ckiXNx4LvW8uyqwHCIOsW3kc7CR0LGvTn
+qLN7Sy5keB9uVs4e69nmU2GG6V0SN1QreuQfroIkgsgYyfz6MvMVhmVVLObELkrY
+RfrOSKEyZnB0+kBfyGykvnQcSaUsqFwXmg==
+-----END CERTIFICATE-----
From 9347b1a0f743c99a26ca2c7ac83bb5ce23dcd0ff Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 12:12:28 -0700
Subject: [PATCH 55/57] fix(ci): Reference token_url for oauth tests
---
.github/workflows/go_tests.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index a9e2137..ad6f2cb 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -37,6 +37,7 @@ jobs:
KEYFACTOR_AUTH_CONFIG_B64: ${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
+ KEYFACTOR_AUTH_URL: ${{ var.KEYFACTOR_AUTH_URL }}
KEYFACTOR_HOSTNAME: ${{ vars.KEYFACTOR_HOSTNAME }}
KEYFACTOR_AUTH_HOSTNAME: ${{ vars.KEYFACTOR_AUTH_HOSTNAME }}
KEYFACTOR_SKIP_VERIFY: ${{ vars.KEYFACTOR_SKIP_VERIFY }}
From 2eb783fc30fe4b463c4db9f8cd975c8d49718e59 Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 12:15:13 -0700
Subject: [PATCH 56/57] fix(ci): Reference token_url for oauth tests
---
.github/workflows/go_tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index ad6f2cb..b61e926 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -37,7 +37,7 @@ jobs:
KEYFACTOR_AUTH_CONFIG_B64: ${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}
KEYFACTOR_AUTH_CLIENT_ID: ${{ secrets.KEYFACTOR_AUTH_CLIENT_ID }}
KEYFACTOR_AUTH_CLIENT_SECRET: ${{ secrets.KEYFACTOR_AUTH_CLIENT_SECRET }}
- KEYFACTOR_AUTH_URL: ${{ var.KEYFACTOR_AUTH_URL }}
+ KEYFACTOR_AUTH_TOKEN_URL: ${{ vars.KEYFACTOR_AUTH_TOKEN_URL }}
KEYFACTOR_HOSTNAME: ${{ vars.KEYFACTOR_HOSTNAME }}
KEYFACTOR_AUTH_HOSTNAME: ${{ vars.KEYFACTOR_AUTH_HOSTNAME }}
KEYFACTOR_SKIP_VERIFY: ${{ vars.KEYFACTOR_SKIP_VERIFY }}
From 16766812e5cc5a8a8037c78dc401e2b8567f44fc Mon Sep 17 00:00:00 2001
From: spbsoluble <1661003+spbsoluble@users.noreply.github.com>
Date: Thu, 31 Oct 2024 12:30:43 -0700
Subject: [PATCH 57/57] fix(ci): Check that lab cert is present
---
.github/workflows/go_tests.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index b61e926..a40e8ba 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -24,6 +24,14 @@ jobs:
- name: Get Public IP
run: curl -s https://api.ipify.org
+ - name: Validate lab cert is present
+ run: |
+ pwd
+ ls -la
+ ls -la lib
+ ls -la lib/certs
+ cat lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.pem
+
- name: Run tests
run: |
if [ -n "${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}" ]; then