Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
91c79c4
chore: update e2e tests to have better handling of testing cert -> is…
irby Oct 24, 2025
5310569
chore: regenerate crd's with 'make generate manifests' command
irby Oct 24, 2025
b9b6325
feat: add health check interval to issuer spec
irby Oct 24, 2025
743d8ff
chore: update docs
irby Oct 24, 2025
9b4c278
feat: implement health check logic
irby Oct 24, 2025
d922417
feat(actions): Ensure that CRDs are not out of date
irby Oct 24, 2025
7067c54
Update generated docs
Oct 24, 2025
2f9f9b9
chore(docs): update changelog version number
irby Oct 24, 2025
52443ce
chore: Convert to healthcheck block instead of healthCheckIntervalSec…
irby Oct 27, 2025
974c919
Update generated docs
Oct 27, 2025
bb90adf
chore: Change the specificaiton update to 2.4
irby Oct 27, 2025
8d8ce73
Add the ability to specify the default issuer timeout across all issu…
JSpon Nov 3, 2025
8339bd1
Merge pull request #57 from JSpon/default-timeout
irby Nov 3, 2025
dca6625
chore: address copilot feedback
irby Nov 11, 2025
d3dc5a3
chore: fix autogenerated CRDs
irby Nov 11, 2025
ba5fb61
Update generated docs
Nov 11, 2025
dace4df
chore: address github copilot feedback
irby Nov 11, 2025
ea06d84
fix: resolve issue with default health check interval override
irby Nov 11, 2025
84e9be9
Update generated docs
Nov 11, 2025
befcbde
Update cmd/main.go
irby Nov 11, 2025
7cbe047
chore: fix some typos and serialization per recommendations
irby Nov 12, 2025
be03357
Merge pull request #55 from Keyfactor/feat/AB#78226/issuer-healthchec…
spbsoluble Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/keyfactor-bootstrap-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
jobs:

build:
name: Build and Lint
name: Build and Check CRDs
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
Expand All @@ -23,11 +23,17 @@ jobs:
cache: true
- run: go mod download
- run: go build -v ./cmd/main.go
- name: Regenerate CRDs
run: make generate manifests
- name: Check for CRD drift
run: |
git diff --compact-summary --exit-code || \
(echo; echo "Unexpected difference in directories after code generation. Run 'make generate manifests' and commit."; exit 1)
# - name: Run linters
# uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0
# with:
# version: latest

test:
name: Go Test
needs: build
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v2.4.0
## Features
- Add a `healthcheck` specification to Issuer / ClusterIssuer resources, allowing flexibility in the health check interval.

# v2.3.1
## Fixes
- Add a manual dispatch of Helm chart release.
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ Command Issuer is installed using a Helm chart. The chart is available in the [C
--create-namespace
```

> For all possible configuration values for the command-cert-manager-issuer Helm chart, please refer to [this list](./deploy/charts/command-cert-manager-issuer/README.md#configuration)

> The Helm chart installs the Command Issuer CRDs by default. The CRDs can be installed manually with the `make install` target.

# Authentication
Expand Down Expand Up @@ -251,6 +253,9 @@ For example, ClusterIssuer resources can be used to issue certificates for resou
| ownerRoleName | The name of the security role assigned as the certificate owner. The security role must be assigned to the identity context of the issuer. If `ownerRoleId` and `ownerRoleName` are both specified, `ownerRoleId` will take precedence. This field is **required** if the enrollment pattern, certificate template, or system-wide setting requires it. |
| scopes | (Optional) Required if using ambient credentials with Azure AKS. If using ambient credentials, these scopes will be put on the access token generated by the ambient credentials' token provider, if applicable. |
| audience | (Optional) If using ambient credentials, this audience will be put on the access token generated by the ambient credentials' token provider, if applicable. Google's ambient credential token provider generates an OIDC ID Token. If this value is not provided, it will default to `command`. |
| healthcheck | (Optional) Defines the health check configuration for the issuer. If omitted, health checks will be enabled and default to 60 seconds. If left disabled, the issuer will not perform a health check when the issuer is healthy and may cause CertificateRequest resources to silently fail. |
| healthcheck.enabled | (Required if health check block provided) Boolean to enable / disable health checks. By default, health checks are enabled. |
| healthcheck.interval | (Optional) Defines the interval between health checks. Example values: `30s`, `1m`, `5.5m`. To prevent overloading the Command instance, this interval must not be less than `30s`. Default value: `60s`. |

> If a different combination of hostname/certificate authority/certificate template is required, a new Issuer or ClusterIssuer resource must be created. Each resource instantiation represents a single configuration.

Expand Down Expand Up @@ -282,6 +287,9 @@ For example, ClusterIssuer resources can be used to issue certificates for resou
# ownerRoleName: "$OWNER_ROLE_NAME" # Uncomment if required
# scopes: "openid email https://example.com/.default" # Uncomment if required
# audience: "https://your-command-url.com" # Uncomment if desired
# healthcheck: # Optional health check configuration
# enabled: true
# interval: 30s
EOF

kubectl -n default apply -f issuer.yaml
Expand Down Expand Up @@ -312,6 +320,9 @@ For example, ClusterIssuer resources can be used to issue certificates for resou
# ownerRoleName: "$OWNER_ROLE_NAME" # Uncomment if required
# scopes: "openid email https://example.com/.default" # Uncomment if required
# audience: "https://your-command-url.com" # Uncomment if desired
# healthcheck: # Optional health check configuration
# enabled: true
# interval: 30s
EOF

kubectl apply -f clusterissuer.yaml
Expand Down
14 changes: 14 additions & 0 deletions api/v1alpha1/issuer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ type IssuerSpec struct {
// +kubebuilder:default:=KeyfactorAPI
APIPath string `json:"apiPath,omitempty"`

// The healthcheck configuration for the issuer. This configures the frequency at which the issuer will perform
// a health check to determine issuer's connectivity to Command instance.
// +kubebuilder:validation:Optional
HealthCheck *HealthCheckConfig `json:"healthcheck,omitempty"`

// EnrollmentPatternId is the ID of the enrollment pattern to use. Supported in Keyfactor Command 25.1 and later.
// If both enrollment pattern and certificate template are specified, enrollment pattern will take precedence.
// If EnrollmentPatternId and EnrollmentPatternName are both specified, EnrollmentPatternId will take precedence.
Expand Down Expand Up @@ -279,6 +284,15 @@ const (
ConditionUnknown ConditionStatus = "Unknown"
)

type HealthCheckConfig struct {
// Determines whether to enable the health check when the issuer is healthy. Default: true
Enabled bool `json:"enabled"`

// The interval at which to health check the issuer when healthy. Defaults to 1 minute. Must not be less than "30s".
// +kubebuilder:validation:Optional
Interval *metav1.Duration `json:"interval,omitempty"`
}

func init() {
SchemeBuilder.Register(&Issuer{}, &IssuerList{})
}
30 changes: 28 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"flag"
"fmt"
"os"
"time"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand Down Expand Up @@ -64,6 +65,7 @@ func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var healthCheckInterval string
var secureMetrics bool
var enableHTTP2 bool
var clusterResourceNamespace string
Expand All @@ -79,6 +81,8 @@ func main() {
"If set the metrics endpoint is served securely")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.StringVar(&healthCheckInterval, "default-health-check-interval", "60s",
"If set, it is the default health check interval for issuers.")
flag.StringVar(&clusterResourceNamespace, "cluster-resource-namespace", "", "The namespace for secrets in which cluster-scoped resources are found.")
flag.BoolVar(&disableApprovedCheck, "disable-approved-check", false,
"Disables waiting for CertificateRequests to have an approved condition before signing.")
Expand Down Expand Up @@ -168,13 +172,25 @@ func main() {
os.Exit(1)
}

defaultHealthCheckInterval, err := time.ParseDuration(healthCheckInterval)
if err != nil {
setupLog.Error(err, "unable to parse default health check interval")
os.Exit(1)
}

if defaultHealthCheckInterval < time.Duration(30) * time.Second {
setupLog.Error(errors.New(fmt.Sprintf("interval %s is invalid, must be greater than or equal to '30s'", healthCheckInterval)), "invalid health check interval")
os.Exit(1)
}

if err = (&controller.IssuerReconciler{
Client: mgr.GetClient(),
Kind: "Issuer",
ClusterResourceNamespace: clusterResourceNamespace,
SecretAccessGrantedAtClusterLevel: secretAccessGrantedAtClusterLevel,
Scheme: mgr.GetScheme(),
HealthCheckerBuilder: command.NewHealthChecker,
DefaultHealthCheckInterval: defaultHealthCheckInterval,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Issuer")
os.Exit(1)
Expand All @@ -186,6 +202,7 @@ func main() {
ClusterResourceNamespace: clusterResourceNamespace,
SecretAccessGrantedAtClusterLevel: secretAccessGrantedAtClusterLevel,
HealthCheckerBuilder: command.NewHealthChecker,
DefaultHealthCheckInterval: defaultHealthCheckInterval,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ClusterIssuer")
os.Exit(1)
Expand Down
64 changes: 40 additions & 24 deletions config/crd/bases/command-issuer.keyfactor.com_clusterissuers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,58 +68,74 @@ spec:
CertificateAuthorityLogicalName is the logical name of the certificate authority to use
E.g. "Keyfactor Root CA" or "Intermediate CA"
type: string
certificateTemplate:
description: |-
Deprecated. CertificateTemplate is the name of the certificate template to use. If using Keyfactor Command 25.1 or later, use EnrollmentPatternName or EnrollmentPatternId instead.
If both enrollment pattern and certificate template are specified, enrollment pattern will take precedence.
Enrollment will fail if the specified template is not compatible with the enrollment pattern.
Refer to the Keyfactor Command documentation for more information.
type: string
commandSecretName:
description: |-
A reference to a K8s kubernetes.io/basic-auth Secret containing basic auth
credentials for the Command instance configured in Hostname. The secret must
be in the same namespace as the referent. If the
referent is a ClusterIssuer, the reference instead refers to the resource
with the given name in the configured 'cluster resource namespace', which
is set as a flag on the controller component (and defaults to the
namespace that the controller runs in).
type: string
enrollmentPatternId:
description: |-
EnrollmentPatternId is the ID of the enrollment pattern to use. Supported in Keyfactor Command 25.1 and later.
If both enrollment pattern and certificate template are specified, enrollment pattern will take precedence.
If both enrollmentPatternId and enrollmentPatternName are specified, enrollmentPatternId will take precedence.
If EnrollmentPatternId and EnrollmentPatternName are both specified, EnrollmentPatternId will take precedence.
Enrollment will fail if the specified template is not compatible with the enrollment pattern.
Refer to the Keyfactor Command documentation for more information.
type: integer
format: int32
type: integer
enrollmentPatternName:
description: |-
EnrollmentPatternName is the name of the enrollment pattern to use. Supported in Keyfactor Command 25.1 and later.
If both enrollment pattern and certificate template are specified, enrollment pattern will take precedence.
If both enrollmentPatternId and enrollmentPatternName are specified, enrollmentPatternId will take precedence.
If EnrollmentPatternId and EnrollmentPatternName are both specified, EnrollmentPatternId will take precedence.
Enrollment will fail if the specified template is not compatible with the enrollment pattern.
Refer to the Keyfactor Command documentation for more information.
type: string
healthcheck:
description: |-
The healthcheck configuration for the issuer. This configures the frequency at which the issuer will perform
a health check to determine issuer's connectivity to Command instance.
properties:
enabled:
description: 'Determines whether to enable the health check when
the issuer is healthy. Default: true'
type: boolean
interval:
description: The interval at which to health check the issuer
when healthy. Defaults to 1 minute. Must not be less than "30s".
type: string
required:
- enabled
type: object
hostname:
description: Hostname is the hostname of a Keyfactor Command instance.
type: string
ownerRoleId:
description: |-
OwnerRoleId is the ID of the security role assigned as the certificate owner.
The specified security role must be assigned to the authorized identity context.
If OwnerRoleId and OwnerRoleName are both specified, OwnerRoleId will take precedence.
This field is required if the enrollment pattern, certificate template, or system-wide settings has been configured as Required.
type: integer
format: int32
type: integer
ownerRoleName:
description: |-
OwnerRoleName is the name of the security role assigned as the certificate owner. This name must match the existing name of the security role.
The specified security role must be assigned to the authorized identity context.
If OwnerRoleId and OwnerRoleName are both specified, OwnerRoleId will take precedence.
This field is required if the enrollment pattern, certificate template, or system-wide settings has been configured as Required.
type: string
certificateTemplate:
description: |-
CertificateTemplate is the name of the certificate template to use. Deprecated in favor of EnrollmentPattern as of Keyfactor Command 25.1.
If both enrollment pattern and certificate template are specified, enrollment pattern will take precedence.
Enrollment will fail if the specified template is not compatible with the enrollment pattern.
Refer to the Keyfactor Command documentation for more information.
type: string
commandSecretName:
description: |-
A reference to a K8s kubernetes.io/basic-auth Secret containing basic auth
credentials for the Command instance configured in Hostname. The secret must
be in the same namespace as the referent. If the
referent is a ClusterIssuer, the reference instead refers to the resource
with the given name in the configured 'cluster resource namespace', which
is set as a flag on the controller component (and defaults to the
namespace that the controller runs in).
type: string
hostname:
description: Hostname is the hostname of a Keyfactor Command instance.
type: string
scopes:
description: |-
A list of comma separated scopes used when requesting a Bearer token from an ambient token provider implied
Expand Down
Loading
Loading