From 276020da068ee79c08c586391878c20b209261a4 Mon Sep 17 00:00:00 2001 From: Morgan Gangwere <470584+indrora@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:22:57 -0700 Subject: [PATCH 1/6] Merge 2.6.1 to main (#75) * command-cert-manager-issuer 2.6.0: Optional CA, Updated Default Healthcheck Interval, Updated Service Account Token Lifecycle (#70) * feat: Add volume and volume mount for service token * chore: fix sources and add changelog Signed-off-by: Matthew H. Irby * feat(enrollment): make certificateAuthorityLogicalName be optional when using enrollment patterns Signed-off-by: Matthew H. Irby * updated error messaging on csr enrollment Signed-off-by: Matthew H. Irby * Update generated docs * chore: update docs + add e2e test for optional CA Signed-off-by: Matthew H. Irby * Update generated docs * feat(healthcheck): bump default healthcheck interval from 1m to 10m Signed-off-by: Matthew H. Irby * chore(deps): bump go version to 1.26 Signed-off-by: Matthew H. Irby * Update generated docs * chore: address copilot feedback. update linter version Signed-off-by: Matthew H. Irby * Update generated docs * chore(ci): bump controller tools version Signed-off-by: Matthew H. Irby * chore(docs): document new Helm chart values for serviceAccount Signed-off-by: Matthew H. Irby --------- Signed-off-by: Matthew H. Irby Co-authored-by: Sven Rajala Co-authored-by: Keyfactor * command-cert-manger-issuer v2.6.1: Add priorityClassName to Deployment (#74) * command-cert-manager-issuer 2.6.0: Optional CA, Updated Default Healthcheck Interval, Updated Service Account Token Lifecycle (#70) (#71) * feat: Add volume and volume mount for service token * chore: fix sources and add changelog * feat(enrollment): make certificateAuthorityLogicalName be optional when using enrollment patterns * updated error messaging on csr enrollment * Update generated docs * chore: update docs + add e2e test for optional CA * Update generated docs * feat(healthcheck): bump default healthcheck interval from 1m to 10m * chore(deps): bump go version to 1.26 * Update generated docs * chore: address copilot feedback. update linter version * Update generated docs * chore(ci): bump controller tools version * chore(docs): document new Helm chart values for serviceAccount --------- Signed-off-by: Matthew H. Irby Co-authored-by: Matthew H. Irby Co-authored-by: Sven Rajala Co-authored-by: Keyfactor * Add priorityClassName * remove quote * Update README * chore(AB#89226): add docs on how to contribute to the project Signed-off-by: Matthew H. Irby * chore: update CHANGELOG Signed-off-by: Matthew H. Irby * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Matthew H. Irby Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com> Co-authored-by: Sven Rajala Co-authored-by: Keyfactor Co-authored-by: slammajamma28 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Matthew H. Irby Co-authored-by: Matthew H. Irby Co-authored-by: Sven Rajala Co-authored-by: Keyfactor Co-authored-by: slammajamma28 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 6 +++ CONTRIBUTING.md | 30 +++++++---- .../command-cert-manager-issuer/README.md | 52 ++++++++++--------- .../templates/deployment.yaml | 3 ++ .../command-cert-manager-issuer/values.yaml | 2 + 5 files changed, 60 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 382671b..fdde3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v2.6.1 +## Features +- Added [priorityClassName](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) as an optional specification on the Deployment resource. +## Chores +- Updated the `CONTRIBUTING.md` guide with notes for how external contributors can contribute to this project. + # v2.6.0 ## Features - Allow `certificateAuthorityLogicalName` to be optional when using an enrollment pattern. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2669087..1c95169 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,24 @@ # Command Cert Manager Issuer Contribution Guide -## Requirements -- Go (>= 1.24) +## How to contribute + +### External Contributors - Submitting a PR + +Since external contributors can't create branches directly on our repositories, go ahead and open your PR against `main`. Our team will handle retargeting it to the appropriate release branch as part of our internal review process. You may see your PR retargeted or, in some cases, closed and replaced with an internal PR carrying your commits. In either case, your contribution will be preserved. + +### Release cadence + +We have an internal review process for every release we make. This process can sometimes take a few days or weeks depending on resourcing. + +## Development + +### Requirements +- Go (>= 1.26.2) - golangci-lint (>= 2.4.0) ([installation notes](https://github.com/golangci/golangci-lint?tab=readme-ov-file#install-golangci-lint)) - helm (>= 3.x) — required to render chart templates for manifest linting ([installation notes](https://helm.sh/docs/intro/install/)) - conftest — policy testing tool powered by Open Policy Agent; installed automatically by `make lint-manifests` -## Installing dependencies +### Installing dependencies Project dependencies can be installed by running the following: ```bash @@ -19,14 +31,14 @@ The following command can be used to add missing requirements or remove unused m go mod tidy ``` -## Running unit tests +### Running unit tests The following command can be run to run the project unit tests: ```bash go test -v ./... ``` -## Running linters +### Running linters The project uses golangci-lint to lint the codebase. The following command can be run to run the linters: ```bash @@ -39,7 +51,7 @@ or, alternatively: make lint ``` -## Updating generated manifests +### Updating generated manifests This command will update the generated custom resource definitions under `config/crd/bases`: @@ -50,7 +62,7 @@ make generate manifests > [!IMPORTANT] > There is no automated process to automatically update the CRDs under `deploy/charts/command-cert-manager-issuer`. If any changes are made to the CRDs, the generated CRDs under `config/crd/bases` must be copied to `deploy/charts/command-cert-manager-issuer/crds` to ensure the Helm chart is up to date. -## Linting Helm manifests +### Linting Helm manifests The Helm chart under `deploy/charts/command-cert-manager-issuer` is linted with two tools on every PR: - **conftest** — runs custom Rego policies located in the [`policy/`](policy/) directory against the rendered manifests @@ -69,13 +81,13 @@ To inspect the rendered templates without linting: make helm-template ``` -### Adding or modifying policies +#### Adding or modifying policies Rego policies live in [`policy/`](policy/). Each `.rego` file in that directory is evaluated by conftest against every resource in the rendered chart. Add a new `.rego` file to enforce additional rules. For example, `policy/roles.rego` enforces that all `Role` resources declare an explicit namespace. kube-linter checks can be tuned in [.kube-linter.yaml](.kube-linter.yaml). To exclude a check, add its name under the `exclude` key. -## Running end-to-end tests +### Running end-to-end tests A comprehensive end-to-end test suite is available to verify the issuer code works against cert-manager and a Keyfactor Command instance. Instructions on how to run the end-to-end test suite can be found [here](./e2e/README.md). \ No newline at end of file diff --git a/deploy/charts/command-cert-manager-issuer/README.md b/deploy/charts/command-cert-manager-issuer/README.md index 3c83a88..f935358 100644 --- a/deploy/charts/command-cert-manager-issuer/README.md +++ b/deploy/charts/command-cert-manager-issuer/README.md @@ -63,29 +63,33 @@ helm install command-cert-manager-issuer command-issuer/command-cert-manager-iss The following table lists the configurable parameters of the `command-cert-manager-issuer` chart and their default values. -| Parameter | Description | Default | -|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------| -| `replicaCount` | Number of replica command-cert-manager-issuers to run | `1` | -| `image.repository` | Image repository | `ghcr.io/keyfactor/command-cert-manager-issuer` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `image.tag` | Image tag | `""` | -| `imagePullSecrets` | Image pull secrets | `[]` | -| `nameOverride` | Name override | `""` | -| `fullnameOverride` | Full name override | `""` | -| `crd.create` | Specifies if CRDs will be created | `true` | -| `crd.annotations` | Annotations to add to the CRD | `{}` | -| `serviceAccount.create` | Specifies if a service account should be created | `true` | -| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | -| `serviceAccount.name` | Name of the service account to use | `""` (uses the fullname template if `create` is true) | +| Parameter | Description | Default | +|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| `replicaCount` | Number of replica command-cert-manager-issuers to run | `1` | +| `image.repository` | Image repository | `ghcr.io/keyfactor/command-cert-manager-issuer` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.tag` | Image tag | `""` | +| `imagePullSecrets` | Image pull secrets | `[]` | +| `nameOverride` | Name override | `""` | +| `fullnameOverride` | Full name override | `""` | +| `secretConfig.useClusterRoleForSecretAccess` | Specifies if the ServiceAccount should be granted access to the Secret resource using a ClusterRole | `false` | +| `secretConfig.useClusterRoleForConfigMapAccess` | Specifies if the ServiceAccount should be granted access to the ConfigMap resource using a ClusterRole | `false` | +| `crd.create` | Specifies if CRDs will be created | `true` | +| `crd.annotations` | Annotations to add to the CRD | `{}` | +| `serviceAccount.create` | Specifies if a service account should be created | `true` | +| `serviceAccount.labels` | Labels to add to the service account | `{}` | +| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | +| `serviceAccount.name` | Name of the service account to use | `""` (uses the fullname template if `create` is true) | | `serviceAccount.automountServiceAccountToken` | Controls whether Kubernetes automatically mounts the service account token into the pod. When `false` (default), a projected volume is used instead, giving explicit control over token expiration and file permissions. Setting to `true` uses Kubernetes' default token mount, which has no expiration and is less restrictive — only recommended if the projected volume approach causes compatibility issues. | `false` | | `serviceAccount.projectedTokenVolume.expirationSeconds` | Lifetime in seconds of the projected service account token. The kubelet will rotate the token before it expires. Only applies when `automountServiceAccountToken` is `false`. | `3607` | -| `serviceAccount.projectedTokenVolume.defaultMode` | File permission bits for the projected token volume. Only applies when `automountServiceAccountToken` is `false`. | `0444` | -| `podAnnotations` | Annotations for the pod | `{}` | -| `podSecurityContext.runAsNonRoot` | Run pod as non-root | `true` | -| `securityContext` | Security context for the pod | `{}` (with commented out options) | -| `resources` | CPU/Memory resource requests/limits | `{}` (with commented out options) | -| `nodeSelector` | Node labels for pod assignment | `{}` | -| `tolerations` | Tolerations for pod assignment | `[]` | -| `env` | Environmental variables set for pod | `{}` | -| `secretConfig.useClusterRoleForSecretAccess` | Specifies if the ServiceAccount should be granted access to the Secret resource using a ClusterRole | `false` | -| `defaultHealthCheckInterval` | Specifies the default health check interval for issuers | `""` (uses the default in the code which is 10 minutes) | +| `serviceAccount.projectedTokenVolume.defaultMode` | File permission bits for the projected token volume. Only applies when `automountServiceAccountToken` is `false`. | `0444` | +| `podLabels` | Labels for the pod | `{}` | +| `podAnnotations` | Annotations for the pod | `{}` | +| `podSecurityContext.runAsNonRoot` | Run pod as non-root | `true` | +| `securityContext` | Security context for the pod | `{}` (with commented out options) | +| `resources` | CPU/Memory resource requests/limits | `{}` (with commented out options) | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `priorityClassName` | Priority class to set for pod | `""` | +| `defaultHealthCheckInterval` | Specifies the default health check interval for issuers | `""` (uses the default in the code which is 10 minutes)| +| `env` | Environmental variables set for pod | `{}` | diff --git a/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml b/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml index 2f5de7d..4d95538 100644 --- a/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml +++ b/deploy/charts/command-cert-manager-issuer/templates/deployment.yaml @@ -121,4 +121,7 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} terminationGracePeriodSeconds: 10 diff --git a/deploy/charts/command-cert-manager-issuer/values.yaml b/deploy/charts/command-cert-manager-issuer/values.yaml index e715b4c..cb9716f 100644 --- a/deploy/charts/command-cert-manager-issuer/values.yaml +++ b/deploy/charts/command-cert-manager-issuer/values.yaml @@ -89,6 +89,8 @@ nodeSelector: {} tolerations: [] +priorityClassName: "" + defaultHealthCheckInterval: "" env: {} From b7234442092347eccc5e7ed21db073031e1ce1b1 Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Thu, 3 Sep 2026 17:00:58 -0400 Subject: [PATCH 2/6] temp: log expiration of access token generated by ambient credentials Signed-off-by: Matthew H. Irby --- internal/command/command.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/command/command.go b/internal/command/command.go index 2af71e4..7bd63d7 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -32,6 +32,7 @@ import ( v1 "github.com/Keyfactor/keyfactor-go-client-sdk/v25/api/keyfactor/v1" cmpki "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/go-logr/logr" + "github.com/golang-jwt/jwt/v5" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -232,6 +233,18 @@ func newServerConfig(ctx context.Context, config *Config) (*auth_providers.Serve return nil, err } + if parsed, _, parseErr := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{}); parseErr == nil { + if claims, ok := parsed.Claims.(jwt.MapClaims); ok { + if exp, expErr := claims.GetExpirationTime(); expErr == nil && exp != nil { + log.Info("ambient access token expiry", + "expiresAt", exp.UTC().Format(time.RFC3339), + "validFor", time.Until(exp.Time).String()) + } + } + } + + log.Info("generating OAuth configuration using access token generated from ambient credentials") + oauthConfig := auth_providers.NewOAuthAuthenticatorBuilder(). WithAccessToken(token). WithCaCertificatePath("") From fdbea0aebd1c9bb1b27c45a9e353f9fe01c937bb Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 8 Sep 2026 16:22:04 -0400 Subject: [PATCH 3/6] fix(oauth): add external token source to abstract ambient token credential generation Signed-off-by: Matthew H. Irby --- go.mod | 35 +++--- go.sum | 66 ++++++----- internal/command/client.go | 216 ++++++++++++++++++------------------ internal/command/command.go | 41 ++----- 4 files changed, 168 insertions(+), 190 deletions(-) diff --git a/go.mod b/go.mod index 6583b09..852b7d0 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/Keyfactor/command-cert-manager-issuer go 1.26.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.3.1 - github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2 + github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2 + github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1 github.com/cert-manager/cert-manager v1.16.2 github.com/go-logr/logr v1.4.3 github.com/stretchr/testify v1.11.1 @@ -24,6 +24,13 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect @@ -35,13 +42,13 @@ require ( require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/Azure/go-ntlmssp v0.1.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -57,7 +64,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -86,12 +93,12 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.46.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.48.0 - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.10.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.223.0 diff --git a/go.sum b/go.sum index bf30c03..d7461ce 100644 --- a/go.sum +++ b/go.sum @@ -4,29 +4,29 @@ cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74 cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw= github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.3.1 h1:G45WsqH5CqMYSAcNHa7tm9fhvKHsc1BaFQS1X1eCfs4= -github.com/Keyfactor/keyfactor-auth-client-go v1.3.1/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= -github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2 h1:7VsZOYgMHAO2a1eeyVgDKel9TJXXYRQpd1EvSvp8lKA= -github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2/go.mod h1:VnVW8x+pChhnOWBR1PNYPeCQQjlWIK1bwHI8i8j7UPI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2 h1:4AsADUisvlpmUrWpva8pcW28W9qykNJYTHbsSd1QIME= +github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1 h1:DWTH0HUaHhxrF4y2XBioL8lXsgU9wwcVCGozBoPaE54= +github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1/go.mod h1:G2VliVSObRN6ciajsmmFLyASUvPWFEe1WjOgxCLileg= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -42,8 +42,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= @@ -78,8 +76,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -102,7 +100,9 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= @@ -167,8 +167,6 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -220,8 +218,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -240,8 +238,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -263,24 +261,24 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -289,8 +287,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/command/client.go b/internal/command/client.go index 28fe7e9..cfe315c 100644 --- a/internal/command/client.go +++ b/internal/command/client.go @@ -20,7 +20,7 @@ import ( "fmt" "io" "net/http" - "strings" + "sync" "time" "context" @@ -37,18 +37,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -var ( - tokenCredentialSource TokenCredentialSource -) - -func getAmbientTokenCredentialSource() TokenCredentialSource { - return tokenCredentialSource -} - -func setAmbientTokenCredentialSource(source TokenCredentialSource) { - tokenCredentialSource = source -} - type Client interface { EnrollCSR(v1.ApiCreateEnrollmentCSRRequest) (*v1.CSSCMSDataModelModelsEnrollmentCSREnrollmentResponse, *http.Response, error) GetAllMetadataFields(v1.ApiGetMetadataFieldsRequest) ([]v1.CSSCMSDataModelModelsMetadataType, error) @@ -91,14 +79,6 @@ func (c *clientAdapter) TestConnection() error { return c.testConnection() } -type TokenCredentialSource interface { - GetAccessToken(context.Context) (string, error) -} - -var ( - _ TokenCredentialSource = &azure{} -) - func getValueOrDefault(configValue string, defaultValue string) string { if configValue != "" { return configValue @@ -106,149 +86,165 @@ func getValueOrDefault(configValue string, defaultValue string) string { return defaultValue } -type azure struct { +type azureTokenSource struct { + ctx context.Context cred azcore.TokenCredential scopes []string + + mu sync.Mutex + last string // last issued token, used to detect rotation + claimsShown bool // whether the JWT claims debug block has been printed yet } -// GetAccessToken implements TokenCredential. -func (a *azure) GetAccessToken(ctx context.Context) (string, error) { - log := log.FromContext(ctx) +var ( + _ oauth2.TokenSource = &azureTokenSource{} +) +func (a *azureTokenSource) Token() (*oauth2.Token, error) { // Try Azure with a short timeout - timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + timeoutCtx, cancel := context.WithTimeout(a.ctx, 10*time.Second) defer cancel() - // To prevent clogging logs every time JWT is generated - initializing := a.cred == nil - - // Lazily create the credential if needed - if a.cred == nil { - c, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return "", fmt.Errorf("%w: failed to set up Azure Default Credential: %w", errTokenFetchFailure, err) - } - a.cred = c - } - - log.Info(fmt.Sprintf("generating Default Azure Credentials with scopes %s", strings.Join(a.scopes, " "))) - - // Request a token with the provided scopes - token, err := a.cred.GetToken(timeoutCtx, policy.TokenRequestOptions{ + tok, err := a.cred.GetToken(timeoutCtx, policy.TokenRequestOptions{ Scopes: a.scopes, }) if err != nil { - return "", fmt.Errorf("%w: failed to fetch token: %w", errTokenFetchFailure, err) + return nil, err } - tokenString := token.Token - - if initializing { - // Only want to output this once, don't want to output this every time the JWT is generated + // Only log when the underlying token has actually rotated - azidentity + // already caches and renews internally, so most calls return the same + // token and would otherwise flood the logs since Token() is called on + // every outbound request. + a.mu.Lock() + l := log.FromContext(a.ctx) + if tok.Token != a.last { + l.Info(fmt.Sprintf("Access token issued from Azure. Token expires at UTC time %s", tok.ExpiresOn.UTC().Format(time.RFC3339))) + a.last = tok.Token + } + // The claims below (identity, issuer, audience, etc.) are tied to the + // underlying identity, not the individual token, so they don't change + // across rotations - only print them once per source instantiation. + if !a.claimsShown { + l.Info("==== BEGIN DEBUG: DefaultAzureCredential JWT ======") + printClaims(l, tok.Token, []string{"aud", "appid", "azp", "iss", "sub", "oid"}) + l.Info("==== END DEBUG: DefaultAzureCredential JWT ======") + a.claimsShown = true + } + a.mu.Unlock() - log.Info("==== BEGIN DEBUG: DefaultAzureCredential JWT ======") + return &oauth2.Token{ + AccessToken: tok.Token, + TokenType: "Bearer", + Expiry: tok.ExpiresOn, + }, nil +} - printClaims(log, tokenString, []string{"aud", "appid", "azp", "iss", "sub", "oid"}) +func newAzureTokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) { + log := log.FromContext(ctx) + log.Info("creating new Azure Default Token Source") - log.Info("==== END DEBUG: DefaultAzureCredential JWT ======") + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to set up Azure Default Credential: %w", errTokenFetchFailure, err) } - log.Info("fetched token using Azure DefaultAzureCredential") - return tokenString, nil -} - -func newAzureDefaultCredentialSource(ctx context.Context, scopes []string) (*azure, error) { - source := &azure{ + // ctx is captured here and reused for every future Token() call for the + // lifetime of this source, since the resulting client is cached + // indefinitely by ClientCache. This is only safe because ctx is the + // manager's root context (passed through unmodified from Reconcile), + // not a per-reconcile-scoped one - see IssuerReconciler.Reconcile and + // cmd/main.go, neither of which set a ReconciliationTimeout. If a + // per-reconcile timeout is ever introduced, every cached source will + // start failing with "context canceled" on its next call. + src := &azureTokenSource{ + ctx: ctx, + cred: cred, scopes: scopes, } - _, err := source.GetAccessToken(ctx) - if err != nil { + + // Fail fast if the credentials/scopes are wrong, same as + // newAzureDefaultCredentialSource does today. + if _, err := src.Token(); err != nil { return nil, err } - tokenCredentialSource = source - - return source, nil + return src, nil } -var ( - _ TokenCredentialSource = &gcp{} -) +type gcpTokenSource struct { + ctx context.Context + mu sync.Mutex + inner oauth2.TokenSource + last string // last issued token, used to detect rotation + audience string + scopes []string -type gcp struct { - tokenSource oauth2.TokenSource - audience string - scopes []string + claimsShown bool // whether the JWT claims debug block has been printed yet } -// GetAccessToken implements TokenCredential. -func (g *gcp) GetAccessToken(ctx context.Context) (string, error) { - log := log.FromContext(ctx) - - // To prevent clogging logs every time JWT is generated - initializing := g.tokenSource == nil +var ( + _ oauth2.TokenSource = &gcpTokenSource{} +) - // Lazily create the TokenSource if it's nil. - if g.tokenSource == nil { - log.Info(fmt.Sprintf("generating default Google credentials with scopes: %s", strings.Join(g.scopes, " "))) +func (g *gcpTokenSource) Token() (*oauth2.Token, error) { + g.mu.Lock() + defer g.mu.Unlock() + log := log.FromContext(g.ctx) - credentials, err := google.FindDefaultCredentials(ctx, g.scopes...) + if g.inner == nil { + log.Info("initializing GCP token source") + creds, err := google.FindDefaultCredentials(g.ctx, g.scopes...) if err != nil { - return "", fmt.Errorf("%w: failed to find GCP ADC: %w", errTokenFetchFailure, err) + return nil, fmt.Errorf("%w: failed to find GCP ADC: %w", errTokenFetchFailure, err) } - log.Info("generating a Google OIDC ID token...") - // Default audience to "command" if not provided aud := getValueOrDefault(g.audience, "command") - - log.Info(fmt.Sprintf("generating Google id token with audience %s", aud)) - - // Use credentials to generate a JWT (requires a service account) - tokenSource, err := idtoken.NewTokenSource(ctx, aud, idtoken.WithCredentialsJSON(credentials.JSON)) - if err != nil { - return "", fmt.Errorf("%w: failed to get GCP ID Token Source: %w", errTokenFetchFailure, err) - } - - _, err = tokenSource.Token() + ts, err := idtoken.NewTokenSource(g.ctx, aud, idtoken.WithCredentialsJSON(creds.JSON)) if err != nil { - return "", fmt.Errorf("%w: failed to generate GCP JWT Token from token source: %w", errTokenFetchFailure, err) + return nil, fmt.Errorf("%w: failed to get GCP ID Token Source: %w", errTokenFetchFailure, err) } - g.tokenSource = tokenSource + g.inner = ts } - // Retrieve the token from the token source. - token, err := g.tokenSource.Token() + token, err := g.inner.Token() if err != nil { - return "", fmt.Errorf("%w: failed to fetch token from GCP ADC token source: %w", errTokenFetchFailure, err) + return nil, fmt.Errorf("%w: failed to fetch token from GCP ADC token source: %w", errTokenFetchFailure, err) } - if initializing { - // Only want to output this once, don't want to output this every time the JWT is generated + if token.AccessToken != g.last { + log.Info(fmt.Sprintf("Access token issued from GCP. Token expires at UTC time %s", token.Expiry.UTC().Format(time.RFC3339))) + g.last = token.AccessToken + } + // The claims below (identity, issuer, audience, etc.) are tied to the + // underlying identity, not the individual token, so they don't change + // across rotations - only print them once per source instantiation. + if !g.claimsShown { log.Info("==== BEGIN DEBUG: Default Google ID Token JWT ======") - printClaims(log, token.AccessToken, []string{"aud", "iss", "sub", "email"}) - log.Info("==== END DEBUG: Default Google ID Token JWT ======") + g.claimsShown = true } - log.Info("fetched token using GCP ApplicationDefaultCredential") - - return token.AccessToken, nil + return token, nil } -func newGCPDefaultCredentialSource(ctx context.Context, audience string, scopes []string) (*gcp, error) { - source := &gcp{ - scopes: scopes, +func newGCPTokenSource(ctx context.Context, audience string, scopes []string) (oauth2.TokenSource, error) { + src := &gcpTokenSource{ + ctx: ctx, audience: audience, + scopes: scopes, } - _, err := source.GetAccessToken(ctx) - if err != nil { + + // Fail fast if the credentials/scopes are wrong, same as your + // newAzureDefaultCredentialSource does today. + if _, err := src.Token(); err != nil { return nil, err } - tokenCredentialSource = source - return source, nil + + return src, nil } func printClaims(log logr.Logger, token string, claimsToPrint []string) { diff --git a/internal/command/command.go b/internal/command/command.go index 7bd63d7..8cf1b5f 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -32,7 +32,6 @@ import ( v1 "github.com/Keyfactor/keyfactor-go-client-sdk/v25/api/keyfactor/v1" cmpki "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/go-logr/logr" - "github.com/golang-jwt/jwt/v5" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -208,45 +207,23 @@ func newServerConfig(ctx context.Context, config *Config) (*auth_providers.Serve // If direct basic-auth/OAuth credentials were configured, continue. Otherwise, // we look for ambient credentials configured on the environment where we're running. if !nonAmbientCredentialsConfigured { - source := getAmbientTokenCredentialSource() - if source == nil { - log.Info("no direct credentials provided; attempting to use ambient credentials. trying Azure DefaultAzureCredential first") + log.Info("no direct credentials provided; attempting to use ambient credentials. trying Azure DefaultAzureCredential first") - var err error - source, err = newAzureDefaultCredentialSource(ctx, config.AmbientCredentialScopes) - if err != nil { - log.Info("couldn't obtain Azure DefaultAzureCredential. trying GCP ApplicationDefaultCredentials", "error", err) - - var innerErr error - source, innerErr = newGCPDefaultCredentialSource(ctx, config.AmbientCredentialAudience, config.AmbientCredentialScopes) - if innerErr != nil { - return nil, fmt.Errorf("%w: azure err: %w. gcp err: %w", errAmbientCredentialCreationFailure, err, innerErr) - } - } - - // Set the credential source globally - setAmbientTokenCredentialSource(source) - } - - token, err := source.GetAccessToken(ctx) + source, err := newAzureTokenSource(ctx, config.AmbientCredentialScopes) if err != nil { - return nil, err - } + log.Info("couldn't obtain Azure DefaultAzureCredential. trying GCP ApplicationDefaultCredentials", "error", err) - if parsed, _, parseErr := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{}); parseErr == nil { - if claims, ok := parsed.Claims.(jwt.MapClaims); ok { - if exp, expErr := claims.GetExpirationTime(); expErr == nil && exp != nil { - log.Info("ambient access token expiry", - "expiresAt", exp.UTC().Format(time.RFC3339), - "validFor", time.Until(exp.Time).String()) - } + var innerErr error + source, innerErr = newGCPTokenSource(ctx, config.AmbientCredentialAudience, config.AmbientCredentialScopes) + if innerErr != nil { + return nil, fmt.Errorf("%w: azure err: %w. gcp err: %w", errAmbientCredentialCreationFailure, err, innerErr) } } - log.Info("generating OAuth configuration using access token generated from ambient credentials") + log.Info("generating OAuth configuration using an external token source generated from ambient credentials") oauthConfig := auth_providers.NewOAuthAuthenticatorBuilder(). - WithAccessToken(token). + WithExternalTokenSource(source). WithCaCertificatePath("") oauthConfig.CommandAuthConfig = authConfig From 60297013f7419547e834f2800641b37317a4b5c3 Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Wed, 9 Sep 2026 11:00:11 -0400 Subject: [PATCH 4/6] chore: bump crypto depenedency. address copilot feedback Signed-off-by: Matthew H. Irby --- go.mod | 10 +++++----- go.sum | 28 ++++++++++++++-------------- internal/command/client.go | 28 ++++++++++++++++------------ 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 852b7d0..9331855 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/crypto v0.52.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect ) @@ -93,12 +94,11 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.10.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.223.0 diff --git a/go.sum b/go.sum index d7461ce..4712760 100644 --- a/go.sum +++ b/go.sum @@ -218,8 +218,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -238,8 +238,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -247,8 +247,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -261,24 +261,24 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -287,8 +287,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/command/client.go b/internal/command/client.go index cfe315c..d02b635 100644 --- a/internal/command/client.go +++ b/internal/command/client.go @@ -109,7 +109,7 @@ func (a *azureTokenSource) Token() (*oauth2.Token, error) { Scopes: a.scopes, }) if err != nil { - return nil, err + return nil, fmt.Errorf("%w: failed to fetch token from Azure Default Credential: %w", errTokenFetchFailure, err) } // Only log when the underlying token has actually rotated - azidentity @@ -151,20 +151,16 @@ func newAzureTokenSource(ctx context.Context, scopes []string) (oauth2.TokenSour // ctx is captured here and reused for every future Token() call for the // lifetime of this source, since the resulting client is cached - // indefinitely by ClientCache. This is only safe because ctx is the - // manager's root context (passed through unmodified from Reconcile), - // not a per-reconcile-scoped one - see IssuerReconciler.Reconcile and - // cmd/main.go, neither of which set a ReconciliationTimeout. If a - // per-reconcile timeout is ever introduced, every cached source will - // start failing with "context canceled" on its next call. + // indefinitely by ClientCache. + ctx = context.WithoutCancel(ctx) + src := &azureTokenSource{ ctx: ctx, cred: cred, scopes: scopes, } - // Fail fast if the credentials/scopes are wrong, same as - // newAzureDefaultCredentialSource does today. + // Fail fast if the credentials/scopes are wrong if _, err := src.Token(); err != nil { return nil, err } @@ -194,7 +190,10 @@ func (g *gcpTokenSource) Token() (*oauth2.Token, error) { if g.inner == nil { log.Info("initializing GCP token source") - creds, err := google.FindDefaultCredentials(g.ctx, g.scopes...) + // Try GCP with a short timeout + timeoutCtx, cancel := context.WithTimeout(g.ctx, 10*time.Second) + defer cancel() + creds, err := google.FindDefaultCredentials(timeoutCtx, g.scopes...) if err != nil { return nil, fmt.Errorf("%w: failed to find GCP ADC: %w", errTokenFetchFailure, err) } @@ -232,14 +231,19 @@ func (g *gcpTokenSource) Token() (*oauth2.Token, error) { } func newGCPTokenSource(ctx context.Context, audience string, scopes []string) (oauth2.TokenSource, error) { + + // ctx is captured here and reused for every future Token() call for the + // lifetime of this source, since the resulting client is cached + // indefinitely by ClientCache. + ctx = context.WithoutCancel(ctx) + src := &gcpTokenSource{ ctx: ctx, audience: audience, scopes: scopes, } - // Fail fast if the credentials/scopes are wrong, same as your - // newAzureDefaultCredentialSource does today. + // Fail fast if the credentials/scopes are wrong if _, err := src.Token(); err != nil { return nil, err } From ec5045da67f69108b28ff4b373b9178ad6c32d60 Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 15 Sep 2026 14:10:31 -0400 Subject: [PATCH 5/6] chore(deps): bump auth library to v1.5.1 Signed-off-by: Matthew H. Irby --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9331855..1e4e412 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Keyfactor/command-cert-manager-issuer go 1.26.2 require ( - github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2 + github.com/Keyfactor/keyfactor-auth-client-go v1.5.1 github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1 github.com/cert-manager/cert-manager v1.16.2 github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index 4712760..191b54f 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ 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.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2 h1:4AsADUisvlpmUrWpva8pcW28W9qykNJYTHbsSd1QIME= -github.com/Keyfactor/keyfactor-auth-client-go v1.5.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.5.1 h1:tDyURVkUQ9EBJwqygcCxV7CXBXrksua8lo1m+Pu9LnE= +github.com/Keyfactor/keyfactor-auth-client-go v1.5.1/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1 h1:DWTH0HUaHhxrF4y2XBioL8lXsgU9wwcVCGozBoPaE54= github.com/Keyfactor/keyfactor-go-client-sdk/v25 v25.0.2-rc.1/go.mod h1:G2VliVSObRN6ciajsmmFLyASUvPWFEe1WjOgxCLileg= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= From ee81aee381867904f40d57503a69969d6fdd1ee8 Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 15 Sep 2026 14:21:52 -0400 Subject: [PATCH 6/6] chore(docs): update CHANGELOG Signed-off-by: Matthew H. Irby --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdde3c3..05f2d62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v2.6.2 +## Fixes +- Fixes an issue where ambient credentials expired with no automatic refresh, causing authentication failures. + # v2.6.1 ## Features - Added [priorityClassName](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) as an optional specification on the Deployment resource.