diff --git a/.github/config/.terraform.lock.hcl b/.github/config/.terraform.lock.hcl
index 567b5b4..d2fb6b0 100644
--- a/.github/config/.terraform.lock.hcl
+++ b/.github/config/.terraform.lock.hcl
@@ -2,24 +2,24 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/integrations/github" {
- version = "6.3.1"
+ version = "6.6.0"
constraints = ">= 6.2.0"
hashes = [
- "h1:fMctJXbbaQU4sBAxAayAVa9wDyIIdSBZX8KzFphKFC0=",
- "zh:25ae1cb97ec528e6b7e9330489f4a33acc0fa80b909c113a8445656bc524c5b9",
- "zh:3e1f6300dc10e52a54f13352770ed79f25ff4ba9ac49b776c52a655a3488a20b",
- "zh:4aaf2877ec22e63358d7c9cd48c7d7947d1a1dc4d03231f0af193d8975d5918a",
- "zh:4b904a81fac12a2a7606c8d811cb9c4e13581adcaaa19e503a067ac95c515925",
- "zh:54fe7e0dca04e698631a5b86bdd43ef09a31375e68f8f89970b4315cd5fc6312",
- "zh:6b14f92cf62784eaf20f43ef58ce966735f30d43deeab077943bd410c0d8b8b2",
- "zh:86c49a1c11c024b26b6750c446f104922a3fe8464d3706a5fb9a4a05c6ca0b0a",
- "zh:8939fb6332c4a58c4e90245eb9f0110987ccafff06b45a7ed513f2759a2abe6a",
- "zh:8b4068a78c1f357325d1151facdb1aff506b9cd79d2bab21a55651255a130e2f",
- "zh:ae22f5e52f534f19811d7f9480b4eb442f12ff16367b3893abb4e449b029ff6b",
- "zh:afae9cfd9d49002ddfea552aa4844074b9974bd56ff2c2458f2297fe0df56a5b",
- "zh:bc7a434408eb16a4fbceec0bd86b108a491408b727071402ad572cdb1afa2eb7",
- "zh:c8e4728ea2d2c6e3d2c1bc5e7d92ed1121c02bab687702ec2748e3a6a0844150",
- "zh:f6314b2cff0c0a07a216501cda51b35e6a4c66a2418c7c9966ccfe701e01b6b0",
+ "h1:Yq0DZYKVFwPdc+v5NnXYcgTYWKInSkjv5WjyWMODj9U=",
+ "zh:0b1b5342db6a17de7c71386704e101be7d6761569e03fb3ff1f3d4c02c32d998",
+ "zh:2fb663467fff76852126b58315d9a1a457e3b04bec51f04bf1c0ddc9dfbb3517",
+ "zh:4183e557a1dfd413dae90ca4bac37dbbe499eae5e923567371f768053f977800",
+ "zh:48b2979f88fb55cdb14b7e4c37c44e0dfbc21b7a19686ce75e339efda773c5c2",
+ "zh:5d803fb06625e0bcf83abb590d4235c117fa7f4aa2168fa3d5f686c41bc529ec",
+ "zh:6f1dd094cbab36363583cda837d7ca470bef5f8abf9b19f23e9cd8b927153498",
+ "zh:772edb5890d72b32868f9fdc0a9a1d4f4701d8e7f8acb37a7ac530d053c776e3",
+ "zh:798f443dbba6610431dcef832047f6917fb5a4e184a3a776c44e6213fb429cc6",
+ "zh:cc08dfcc387e2603f6dbaff8c236c1254185450d6cadd6bad92879fe7e7dbce9",
+ "zh:d5e2c8d7f50f91d6847ddce27b10b721bdfce99c1bbab42a68fa271337d73d63",
+ "zh:e69a0045440c706f50f84a84ff8b1df520ec9bf757de4b8f9959f2ed20c3f440",
+ "zh:efc5358573a6403cbea3a08a2fcd2407258ac083d9134c641bdcb578966d8bdf",
+ "zh:f627a255e5809ec2375f79949c79417847fa56b9e9222ea7c45a463eb663f137",
+ "zh:f7c02f762e4cf1de7f58bde520798491ccdd54a5bd52278d579c146d1d07d4f0",
"zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25",
]
}
diff --git a/.github/config/MODULE.MD b/.github/config/MODULE.MD
index 4a0f4ab..4822c4f 100644
--- a/.github/config/MODULE.MD
+++ b/.github/config/MODULE.MD
@@ -9,14 +9,13 @@
| Name | Version |
|------|---------|
-| [github](#provider\_github) | 6.3.1 |
+| [github](#provider\_github) | 6.6.0 |
## 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 |
+| [keyfactor\_github\_test\_environment\_ses\_2441](#module\_keyfactor\_github\_test\_environment\_ses\_2441) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
## Resources
@@ -28,11 +27,15 @@
| 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\_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://int1230-oauth.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"` | no |
+| [keyfactor\_auth\_token\_url\_ses\_2441](#input\_keyfactor\_auth\_token\_url\_ses\_2441) | The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token | `string` | `"https://auth.kftestlab.com/oauth2/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\_id\_ses\_2441](#input\_keyfactor\_client\_id\_ses\_2441) | 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\_client\_secret\_ses\_2441](#input\_keyfactor\_client\_secret\_ses\_2441) | 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\_hostname\_12\_3\_0\_KC](#input\_keyfactor\_hostname\_12\_3\_0\_KC) | The hostname of the Keyfactor instance | `string` | `"int1230-oauth.eastus2.cloudapp.azure.com"` | no |
+| [keyfactor\_hostname\_ses\_2441](#input\_keyfactor\_hostname\_ses\_2441) | The hostname of the Keyfactor instance | `string` | `"int2441.kftestlab.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 |
diff --git a/.github/config/README.md b/.github/config/README.md
index 42fee46..9380eae 100644
--- a/.github/config/README.md
+++ b/.github/config/README.md
@@ -58,14 +58,13 @@ module "keyfactor_github_test_environment_12_3_0_kc" {
| Name | Version |
|------|---------|
-| [github](#provider\_github) | 6.3.1 |
+| [github](#provider\_github) | 6.6.0 |
## 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 |
+| [keyfactor\_github\_test\_environment\_ses\_2441](#module\_keyfactor\_github\_test\_environment\_ses\_2441) | git::ssh://git@github.com/Keyfactor/terraform-module-keyfactor-github-test-environment-ad.git | main |
## Resources
@@ -77,11 +76,15 @@ module "keyfactor_github_test_environment_12_3_0_kc" {
| 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\_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://int1230-oauth.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"` | no |
+| [keyfactor\_auth\_token\_url\_ses\_2441](#input\_keyfactor\_auth\_token\_url\_ses\_2441) | The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token | `string` | `"https://auth.kftestlab.com/oauth2/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\_id\_ses\_2441](#input\_keyfactor\_client\_id\_ses\_2441) | 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\_client\_secret\_ses\_2441](#input\_keyfactor\_client\_secret\_ses\_2441) | 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\_hostname\_12\_3\_0\_KC](#input\_keyfactor\_hostname\_12\_3\_0\_KC) | The hostname of the Keyfactor instance | `string` | `"int1230-oauth.eastus2.cloudapp.azure.com"` | no |
+| [keyfactor\_hostname\_ses\_2441](#input\_keyfactor\_hostname\_ses\_2441) | The hostname of the Keyfactor instance | `string` | `"int2441.kftestlab.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 |
diff --git a/.github/config/environments.tf b/.github/config/environments.tf
index d26fab7..193e4ee 100644
--- a/.github/config/environments.tf
+++ b/.github/config/environments.tf
@@ -1,13 +1,13 @@
-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
- keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
-}
+# 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
+# keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
+# }
# 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"
@@ -21,15 +21,29 @@ module "keyfactor_github_test_environment_ad_10_5_0" {
# keyfactor_tls_skip_verify = true
# }
-module "keyfactor_github_test_environment_12_3_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_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
+# keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
+# }
+
+module "keyfactor_github_test_environment_ses_2441" {
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_environment_name = "ses_2441"
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_hostname = var.keyfactor_hostname_ses_2441
+ keyfactor_auth_token_url = var.keyfactor_auth_token_url_ses_2441
+ keyfactor_client_id = var.keyfactor_client_id_ses_2441
+ keyfactor_client_secret = var.keyfactor_client_secret_ses_2441
keyfactor_tls_skip_verify = true
+ keyfactor_api_path = "/Keyfactor/API"
keyfactor_config_file = base64encode(file("${path.module}/command_config.json"))
-}
+}
\ No newline at end of file
diff --git a/.github/config/variables.tf b/.github/config/variables.tf
index 29ffd56..55b40db 100644
--- a/.github/config/variables.tf
+++ b/.github/config/variables.tf
@@ -37,3 +37,25 @@ variable "keyfactor_auth_token_url_12_3_0_KC" {
default = "https://int1230-oauth.eastus2.cloudapp.azure.com:8444/realms/Keyfactor/protocol/openid-connect/token"
}
+variable "keyfactor_client_id_ses_2441" {
+ description = "The client ID to authenticate with the Keyfactor instance using Keycloak client credentials"
+ type = string
+}
+
+variable "keyfactor_client_secret_ses_2441" {
+ description = "The client secret to authenticate with the Keyfactor instance using Keycloak client credentials"
+ type = string
+}
+
+variable "keyfactor_hostname_ses_2441" {
+ description = "The hostname of the Keyfactor instance"
+ type = string
+ default = "int2441.kftestlab.com"
+
+}
+
+variable "keyfactor_auth_token_url_ses_2441" {
+ description = "The hostname of the KeyCloak instance to authenticate to for a Keyfactor Command access token"
+ type = string
+ default = "https://auth.kftestlab.com/oauth2/token"
+}
\ No newline at end of file
diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml
index 6a09a83..bcb842a 100644
--- a/.github/workflows/go_tests.yml
+++ b/.github/workflows/go_tests.yml
@@ -12,7 +12,8 @@ jobs:
matrix:
environment:
# - "KFC_10_5_0"
- - "KFC_12_3_0_KC"
+# - "KFC_12_3_0_KC"
+ - "ses_2441"
environment: ${{ matrix.environment }}
steps:
- name: Check out code
@@ -21,15 +22,11 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
- go-version: 1.22
+ go-version: 1.24
- name: Get Public IP
run: curl -s https://api.ipify.org
- - name: Validate lab cert is present
- run: |
- cat lib/certs/int-oidc-lab.eastus2.cloudapp.azure.com.crt
-
- name: Run tests
run: |
if [ -n "${{ secrets.KEYFACTOR_AUTH_CONFIG_B64 }}" ]; then
@@ -48,4 +45,6 @@ jobs:
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
+ TEST_KEYFACTOR_OAUTH: ${{ vars.TEST_KEYFACTOR_OAUTH }}
+ TEST_UNTRUSTED_CERT: ${{ vars.TEST_UNTRUSTED_CERT }}
+ KEYFACTOR_API_PATH: ${{ vars.KEYFACTOR_API_PATH }}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0978c43..b5a8d32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+# v1.3.0
+
+## Features
+- Add support for fetching an oauth2 token using the `client_credentials` grant type without connecting to Keyfactor Command.
+- Add placeholders for omitted `Authorization` header in the `curl` command string output in trace logging.
+
+## Bug Fixes
+- Log `curl` command string at `trace` level after request is sent to include any transport mutations.
+
+## Chores
+- Bump Go version to `1.24`.
+
# v1.2.0
## Features
diff --git a/auth_providers/auth_basic_test.go b/auth_providers/auth_basic_test.go
index 5c404ae..5aa3c50 100644
--- a/auth_providers/auth_basic_test.go
+++ b/auth_providers/auth_basic_test.go
@@ -25,8 +25,8 @@ 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" {
+ // Skip test if TEST_KEYFACTOR_OAUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_OAUTH") == "1" || os.Getenv("TEST_KEYFACTOR_OAUTH") == "true" {
t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
return
}
@@ -46,8 +46,8 @@ 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" {
+ // Skip test if TEST_KEYFACTOR_OAUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_OAUTH") == "1" || os.Getenv("TEST_KEYFACTOR_OAUTH") == "true" {
t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
return
}
@@ -63,8 +63,8 @@ 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" {
+ // Skip test if TEST_KEYFACTOR_OAUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_OAUTH") == "1" || os.Getenv("TEST_KEYFACTOR_OAUTH") == "true" {
t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
return
}
@@ -85,8 +85,8 @@ 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" {
+ // Skip test if TEST_KEYFACTOR_OAUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_OAUTH") == "1" || os.Getenv("TEST_KEYFACTOR_OAUTH") == "true" {
t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
return
}
@@ -230,8 +230,8 @@ 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" {
+ // Skip test if TEST_KEYFACTOR_OAUTH is set to 1 or true
+ if os.Getenv("TEST_KEYFACTOR_OAUTH") == "1" || os.Getenv("TEST_KEYFACTOR_OAUTH") == "true" {
t.Skip("Skipping TestBasicAuthAuthenticator_GetHttpClient")
return
}
diff --git a/auth_providers/auth_core.go b/auth_providers/auth_core.go
index 91f3504..538a22a 100644
--- a/auth_providers/auth_core.go
+++ b/auth_providers/auth_core.go
@@ -501,12 +501,13 @@ func (c *CommandAuthConfig) Authenticate() error {
}
c.HttpClient.Timeout = time.Duration(c.HttpClientTimeout) * time.Second
- curlStr, cErr := RequestToCurl(req)
- if cErr == nil {
+
+ cResp, cErr := c.HttpClient.Do(req)
+ curlStr, curlErr := RequestToCurl(req)
+ if curlErr == nil {
log.Printf("[TRACE] curl command: %s", curlStr)
}
- cResp, cErr := c.HttpClient.Do(req)
if cErr != nil {
return cErr
} else if cResp == nil {
@@ -514,6 +515,7 @@ func (c *CommandAuthConfig) Authenticate() error {
}
defer cResp.Body.Close()
+ log.Printf("[DEBUG] request to Keyfactor Command API returned status code %d", cResp.StatusCode)
// check if body is empty
if cResp.Body == nil {
@@ -798,19 +800,56 @@ func RequestToCurl(req *http.Request) (string, error) {
// Add headers
for name, values := range req.Header {
for _, value := range values {
+ // check if is Authorization header and skip it
+ if strings.EqualFold(name, "Authorization") {
+ // check if basic auth and skip it
+ if strings.HasPrefix(value, "Basic ") {
+ // Remove credentials and put in env variables as placeholder
+ log.Printf(
+ "[DEBUG] Found Basic auth in Authorization header, " +
+ "replacing with env variable references",
+ )
+ curlCommand.WriteString(
+ fmt.Sprintf(
+ "-H %q ", fmt.Sprintf(
+ "%s: Basic $(echo -n $\"%s,$%s\" | base64)", name,
+ EnvKeyfactorUsername, EnvKeyfactorPassword,
+ ),
+ ),
+ )
+ continue
+ } else if strings.HasPrefix(value, "Bearer ") {
+ // Remove credentials and put in env variables as placeholder
+ log.Printf("[DEBUG] Found Bearer token in Authorization header, replacing with kfutil command to fetch token")
+ curlCommand.WriteString(
+ fmt.Sprintf(
+ "-H %q ", fmt.Sprintf(
+ "%s: Bearer $(kfutil auth fetch-oauth-token)", name,
+ ),
+ ),
+ )
+ continue
+ } else {
+ // Skip other Authorization headers
+ log.Printf("[ERROR] Skipping unhandled Authorization header: %s", name)
+ continue
+ }
+ }
curlCommand.WriteString(fmt.Sprintf("-H %q ", fmt.Sprintf("%s: %s", name, value)))
}
}
// Add the body if it exists
if req.Method == http.MethodPost || req.Method == http.MethodPut {
- body, err := io.ReadAll(req.Body)
- if err != nil {
- return "", err
- }
- req.Body = io.NopCloser(bytes.NewBuffer(body)) // Restore the request body
+ if req.Body != nil {
+ body, err := io.ReadAll(req.Body)
+ if err != nil {
+ return "", err
+ }
+ req.Body = io.NopCloser(bytes.NewBuffer(body)) // Restore the request body
- curlCommand.WriteString(fmt.Sprintf("--data %q ", string(body)))
+ curlCommand.WriteString(fmt.Sprintf("--data %q ", string(body)))
+ }
}
return curlCommand.String(), nil
diff --git a/auth_providers/auth_core_test.go b/auth_providers/auth_core_test.go
index efcf2a9..43eb765 100644
--- a/auth_providers/auth_core_test.go
+++ b/auth_providers/auth_core_test.go
@@ -16,6 +16,7 @@ package auth_providers_test
import (
"net/http"
+ "strings"
"testing"
"github.com/Keyfactor/keyfactor-auth-client-go/auth_providers"
@@ -102,3 +103,75 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Q2+1+2+1+2+1+2+1+2+
t.Fatalf("expected non-zero blocks")
}
}
+
+func TestRequestToCurl(t *testing.T) {
+ tests := []struct {
+ name string
+ method string
+ url string
+ headers map[string]string
+ wantInCurl []string
+ notWantInCurl []string
+ }{
+ {
+ name: "Basic Auth",
+ method: "GET",
+ url: "https://example.com/api",
+ headers: map[string]string{
+ "Authorization": "Basic dXNlcjpwYXNz",
+ },
+ wantInCurl: []string{
+ "curl", "-X", "GET", "https://example.com/api",
+ "-H", "Authorization: Basic",
+ },
+ notWantInCurl: []string{
+ "Authorization: Basic dXNlcjpwYXNz",
+ },
+ },
+ {
+ name: "Bearer Auth",
+ method: "POST",
+ url: "https://example.com/token",
+ headers: map[string]string{
+ "Authorization": "Bearer testtoken",
+ "Content-Type": "application/json",
+ },
+ wantInCurl: []string{
+ "curl", "-X", "POST", "https://example.com/token",
+ "-H", "Authorization: Bearer",
+ "-H", "Content-Type: application/json",
+ },
+ notWantInCurl: []string{
+ "Authorization: Bearer testtoken",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ req, err := http.NewRequest(tt.method, tt.url, nil)
+ if err != nil {
+ t.Fatalf("failed to create request: %v", err)
+ }
+ for k, v := range tt.headers {
+ req.Header.Set(k, v)
+ }
+
+ curlStr, err := auth_providers.RequestToCurl(req)
+ if err != nil {
+ t.Errorf("%s: RequestToCurl returned error: %v", tt.name, err)
+ continue
+ }
+ for _, want := range tt.wantInCurl {
+ if !strings.Contains(curlStr, want) {
+ t.Errorf("%s: curl string missing %q\nGot: %s", tt.name, want, curlStr)
+ }
+ }
+
+ for _, notWant := range tt.notWantInCurl {
+ if strings.Contains(curlStr, notWant) {
+ t.Errorf("%s: curl string contains unwanted %q\nGot: %s", tt.name, notWant, curlStr)
+ }
+ }
+ t.Logf("%s: curl command: %s", tt.name, curlStr)
+ }
+}
diff --git a/auth_providers/auth_oauth.go b/auth_providers/auth_oauth.go
index da9dd32..d402b75 100644
--- a/auth_providers/auth_oauth.go
+++ b/auth_providers/auth_oauth.go
@@ -18,6 +18,7 @@ import (
"context"
"crypto/x509"
"fmt"
+ "log"
"net/http"
"os"
"strings"
@@ -439,16 +440,79 @@ func (b *CommandConfigOauth) GetServerConfig() *Server {
return &server
}
+// GetAccessToken returns the OAuth2 token source for the given configuration.
+func (b *CommandConfigOauth) GetAccessToken() (*oauth2.Token, error) {
+ if b == nil {
+ return nil, fmt.Errorf("CommandConfigOauth is nil")
+ }
+
+ _ = b.ValidateAuthConfig() // sets client config if not already set but eats the error so that we can return and
+ // error fetching the token
+
+ if b.AccessToken != "" && (b.ClientID == "" || b.ClientSecret == "" || b.TokenURL == "") {
+ log.Printf("[DEBUG] Access token is explicitly set, and no client credentials are provided. Using static token source.")
+ return &oauth2.Token{
+ AccessToken: b.AccessToken,
+ TokenType: DefaultTokenPrefix,
+ Expiry: b.Expiry,
+ }, nil
+ }
+
+ log.Printf("[DEBUG] Getting OAuth2 token source for client ID: %s", b.ClientID)
+ if b.ClientID == "" || b.ClientSecret == "" || b.TokenURL == "" {
+ return nil, fmt.Errorf("client ID, client secret, and token URL must be provided")
+ }
+
+ config := &clientcredentials.Config{
+ ClientID: b.ClientID,
+ ClientSecret: b.ClientSecret,
+ TokenURL: b.TokenURL,
+ Scopes: b.Scopes,
+ }
+
+ if b.Audience != "" {
+ log.Printf("[DEBUG] Setting audience for OAuth2 token source: %s", b.Audience)
+ config.EndpointParams = map[string][]string{
+ "audience": {b.Audience},
+ }
+ }
+
+ ctx := context.Background()
+ log.Printf("[DEBUG] Returning call config.TokenSource() for client ID: %s", b.ClientID)
+ tokenSource := config.TokenSource(ctx)
+ if tokenSource == nil {
+ return nil, fmt.Errorf("failed to create token source for client ID: %s", b.ClientID)
+ }
+ token, tErr := tokenSource.Token()
+ if tErr != nil {
+ return nil, fmt.Errorf("failed to retrieve token for client ID %s: %w", b.ClientID, tErr)
+ }
+ if token == nil || token.AccessToken == "" {
+ return nil, fmt.Errorf("received empty OAuth token for client ID: %s", b.ClientID)
+ }
+
+ return token, nil
+}
+
// RoundTrip executes a single HTTP transaction, adding the OAuth2 token to the request
func (t *oauth2Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ log.Printf("[DEBUG] Attempting to get oAuth token from: %s %s", req.Method, req.URL)
token, err := t.src.Token()
if err != nil {
+
return nil, fmt.Errorf("failed to retrieve OAuth token: %w", err)
}
+ if token == nil || token.AccessToken == "" {
+ return nil, fmt.Errorf("received empty OAuth token")
+ }
+
// Clone the request to avoid mutating the original
+ log.Printf("[DEBUG] Adding oAuth token to request: %s %s", req.Method, req.URL)
reqCopy := req.Clone(req.Context())
token.SetAuthHeader(reqCopy)
+ requestCurlStr, _ := RequestToCurl(reqCopy)
+ log.Printf("[TRACE] curl command: %s", requestCurlStr)
return t.base.RoundTrip(reqCopy)
}
diff --git a/auth_providers/auth_oauth_test.go b/auth_providers/auth_oauth_test.go
index 1d8e565..720a9ae 100644
--- a/auth_providers/auth_oauth_test.go
+++ b/auth_providers/auth_oauth_test.go
@@ -188,15 +188,22 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
// end test case
// Begin test case
- noParamsConfig = &auth_providers.CommandConfigOauth{}
- httpsFailEnvExpected := []string{"tls: failed to verify certificate"}
- authOauthTest(
- t,
- fmt.Sprintf("w/o env %s", auth_providers.EnvKeyfactorCACert),
- true,
- noParamsConfig,
- httpsFailEnvExpected...,
- )
+
+ if os.Getenv("TEST_UNTRUSTED_CERT") == "1" || os.Getenv("TEST_UNTRUSTED_CERT") == "true" {
+ noParamsConfig = &auth_providers.CommandConfigOauth{}
+ httpsFailEnvExpected := []string{"tls: failed to verify certificate"}
+ t.Log("Testing oAuth with https fail env")
+ t.Logf("Setting environment variable %s", auth_providers.EnvKeyfactorSkipVerify)
+ os.Setenv(auth_providers.EnvKeyfactorSkipVerify, "false")
+ 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")
@@ -256,7 +263,7 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
}
fullParamsInvalidPassConfig.WithSkipVerify(true)
invalidCredsExpectedError := []string{
- "oauth2", "unauthorized_client", "Invalid client or Invalid client credentials",
+ "oauth2", "fail", "invalid", "client",
}
authOauthTest(t, "w/ full params & invalid pass", true, fullParamsInvalidPassConfig, invalidCredsExpectedError...)
@@ -314,15 +321,17 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
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")
- httpsFailConfigFile := &auth_providers.CommandConfigOauth{}
- httpsFailConfigFile.
- WithConfigProfile("oauth")
- httpsFailConfigFileExpected := []string{"tls: failed to verify certificate"}
- authOauthTest(
- t, "oAuth with valid implicit config file https fail", true, httpsFailConfigFile,
- httpsFailConfigFileExpected...,
- )
+ if os.Getenv("TEST_UNTRUSTED_CERT") == "1" || os.Getenv("TEST_UNTRUSTED_CERT") == "true" {
+ 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"}
+ authOauthTest(
+ t, "oAuth with valid implicit config file https fail", true, httpsFailConfigFile,
+ httpsFailConfigFileExpected...,
+ )
+ }
t.Log("Testing oAuth with invalid profile implicit config file")
invProfile := &auth_providers.CommandConfigOauth{}
@@ -346,6 +355,18 @@ func TestCommandConfigOauth_Authenticate(t *testing.T) {
authOauthTest(t, "with invalid creds implicit config file", true, invCmdHost, invHostExpectedError...)
}
+func TestCommandConfigOauth_GetAccessToken(t *testing.T) {
+ clientID, clientSecret, tokenURL := exportOAuthEnvVariables()
+ t.Log("Testing auth with w/ full params variables")
+ fullParamsConfig := &auth_providers.CommandConfigOauth{
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ TokenURL: tokenURL,
+ }
+ fullParamsConfig.WithSkipVerify(true)
+ authOauthTest(t, "w/ GetAccessToken w/ full params variables", false, fullParamsConfig)
+}
+
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" {
@@ -376,6 +397,23 @@ func authOauthTest(
t.Run(
fmt.Sprintf("oAuth Auth Test %s", testName), func(t *testing.T) {
+ // oauth credentials should always generate an access token
+ oauthToken, tErr := config.GetAccessToken()
+ if !allowFail {
+ if tErr != nil {
+ t.Errorf("oAuth auth test '%s' failed to get token source with %v", testName, tErr)
+ t.FailNow()
+ return
+ }
+
+ if oauthToken == nil || oauthToken.AccessToken == "" {
+ t.Errorf("oAuth auth test '%s' failed to get token source", testName)
+ t.FailNow()
+ return
+ }
+ //t.Logf("token %s", at.AccessToken)
+ t.Logf("oAuth auth test '%s' succeeded", testName)
+ }
err := config.Authenticate()
if allowFail {
if err == nil {
@@ -472,23 +510,23 @@ func DownloadCertificate(input string, outputPath string) error {
// Set default output path to current working directory if none is provided
if outputPath == "" {
- cwd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("failed to get current working directory: %v", err)
+ cwd, cwdErr := os.Getwd()
+ if cwdErr != nil {
+ return fmt.Errorf("failed to get current working directory: %v", cwdErr)
}
outputPath = cwd
}
// Ensure the output directory exists
- if err := os.MkdirAll(outputPath, os.ModePerm); err != nil {
- return fmt.Errorf("failed to create output directory: %v", err)
+ if dirErr := os.MkdirAll(filepath.Dir(outputPath), os.ModePerm); dirErr != nil {
+ return fmt.Errorf("failed to create output directory: %v", dirErr)
}
// Create the output file
- outputFile := filepath.Join(outputPath, fmt.Sprintf("%s.crt", hostname))
- file, err := os.Create(outputFile)
- if err != nil {
- return fmt.Errorf("failed to create file %s: %v", outputFile, err)
+ outputFile := filepath.Join(outputPath)
+ file, fErr := os.Create(outputFile)
+ if fErr != nil {
+ return fmt.Errorf("failed to create file %s: %v", outputFile, fErr)
}
defer file.Close()
@@ -502,9 +540,9 @@ func DownloadCertificate(input string, outputPath string) error {
}
// Send an HTTP GET request to the server
- resp, err := httpClient.Get(input)
- if err != nil {
- return fmt.Errorf("failed to connect to %s: %v", input, err)
+ resp, respErr := httpClient.Get(input)
+ if respErr != nil {
+ return fmt.Errorf("failed to connect to %s: %v", input, respErr)
}
defer resp.Body.Close()
@@ -516,14 +554,14 @@ func DownloadCertificate(input string, outputPath string) error {
// Write the entire certificate chain to the output file in PEM format
for _, cert := range tlsConnState.PeerCertificates {
- err = pem.Encode(
+ pemErr := pem.Encode(
file, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
},
)
- if err != nil {
- return fmt.Errorf("failed to write certificate to file: %v", err)
+ if pemErr != nil {
+ return fmt.Errorf("failed to write certificate to file: %v", pemErr)
}
}
diff --git a/go.mod b/go.mod
index 1cd9b76..79537f1 100644
--- a/go.mod
+++ b/go.mod
@@ -14,30 +14,32 @@
module github.com/Keyfactor/keyfactor-auth-client-go
-go 1.23
+go 1.24
+
+toolchain go1.24.3
require (
- github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1
- github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0
+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
+ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1
github.com/stretchr/testify v1.10.0
- golang.org/x/oauth2 v0.25.0
+ golang.org/x/oauth2 v0.30.0
gopkg.in/yaml.v2 v2.4.0
)
require (
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect
- github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
+ github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+ github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- golang.org/x/crypto v0.32.0 // indirect
- golang.org/x/net v0.34.0 // indirect
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/crypto v0.39.0 // indirect
+ golang.org/x/net v0.41.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ golang.org/x/text v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 0d38ac5..43c21a3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,33 +1,31 @@
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw=
-github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
-github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
+github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
+github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -38,23 +36,23 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
-github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
+github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
+github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
-golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
-golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
-golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
-golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
-golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
+golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
+golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
+golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=