Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 28 additions & 16 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
name: Build and Test
name: Build Test and Lint
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'main'
- 'release-*.*'
jobs:
build:
name: Build and Lint
name: Build and Test
runs-on: ubuntu-latest
timeout-minutes: 8
timeout-minutes: 10
steps:
# Checkout code
# https://github.com/actions/checkout
Expand All @@ -30,19 +31,13 @@ jobs:
# Build Go binary
- 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)
- run: go test ./...

test:
name: Go Test
lint:
name: Lint
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 10
steps:
# Checkout code
# https://github.com/actions/checkout
Expand All @@ -57,6 +52,23 @@ jobs:
go-version-file: 'go.mod'
cache: true

# Run Go tests
- name: Run go test
run: go test -v ./...
- name: Install Helm
uses: azure/setup-helm@v3.5

# Run Go linters
# https://github.com/golangci/golangci-lint-action
- name: Run linters
uses: golangci/golangci-lint-action@v7
with:
version: v2.4.0

- name: Regenerate CRDs
run: make generate manifests

- name: Check for CRD drift
run: |
git diff --compact-summary --exit-code -- config/crd deploy/charts || \
(echo; echo "Unexpected difference in directories after code generation. Run 'make generate manifests' and commit."; exit 1)

- name: Lint Helm manifests
run: make lint-manifests
58 changes: 36 additions & 22 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 12m

skip-dirs:
- testdata$
- test/mock
- go/pkg/mod

skip-files:
- ".*\\.pb\\.go"

version: "2"
linters:
enable:
- bodyclose
- durationcheck
- errorlint
- goimports
- revive
- gocritic
- gosec
- misspell
- nakedret
- nolintlint
- revive
- unconvert
- unparam
- whitespace
- gocritic
- nolintlint

linters-settings:
revive:
# minimal confidence for issues, default is 0.8
confidence: 0.0
settings:
revive:
confidence: 0
rules:
- name: var-naming
disabled: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- .*\.pb\.go
- testdata$
- test/mock
- third_party$
- builtin$
- examples$
formatters:
enable:
- goimports
exclusions:
generated: lax
paths:
- .*\.pb\.go
- testdata$
- test/mock
- third_party$
- builtin$
- examples$
6 changes: 6 additions & 0 deletions .kube-linter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
checks:
addAllBuiltIn: true
# Add project-specific exclusions below as needed, e.g.:
# exclude:
# - "unset-cpu-requirements"
# - "unset-memory-requirements"
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v2.5.2
## Fixes
- Fixes an issue where a namespace may not be properly applied if applying the Helm template without a namespace specified / using `kubectl apply -f` directly with the rendered template.
- Fixes an issue where the error message from a failed Enrollment API call is not logged.
## Chores
- Update GitHub Actions workflow to check for policy enforcement on Helm chart rendered manifests in addition to checking for drift in generated CRDs.
- Fixes various linting issues in the codebase.

# v2.5.1
## Fixes
- Fixes an issue where OAuth 2.0 client credentials were being regenerated on every API call.
Expand Down
81 changes: 81 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Command Cert Manager Issuer Contribution Guide

## Requirements
- Go (>= 1.24)
- 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
Project dependencies can be installed by running the following:

```bash
go mod download
```

The following command can be used to add missing requirements or remove unused modules:

```bash
go mod tidy
```

## Running unit tests
The following command can be run to run the project unit tests:

```bash
go test -v ./...
```

## Running linters
The project uses golangci-lint to lint the codebase. The following command can be run to run the linters:

```bash
golangci-lint run
```

or, alternatively:

```bash
make lint
```

## Updating generated manifests

This command will update the generated custom resource definitions under `config/crd/bases`:

```bash
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

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

To run both checks locally:

```bash
make lint-manifests
```

`conftest` is downloaded automatically into `bin/` on first use; no manual installation is required.

To inspect the rendered templates without linting:

```bash
make helm-template
```

### 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
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).
42 changes: 40 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ endif
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker

# Helm chart and Conftest policy directory for manifest linting
HELM_CHART_DIR ?= deploy/charts/command-cert-manager-issuer
HELM_RELEASE_NAME ?= command-cert-manager-issuer
POLICY_DIR ?= policy

# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
Expand Down Expand Up @@ -78,6 +83,20 @@ lint: golangci-lint ## Run golangci-lint linter & yamllint
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
$(GOLANGCI_LINT) run --fix

.PHONY: helm-template
helm-template: ## Render Helm chart templates to stdout (includes CRDs).
helm template $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) --include-crds

.PHONY: lint-manifests
lint-manifests: conftest ## Run Conftest policy checks against every CI values file in $(HELM_CHART_DIR)/ci/.
@failed=0; \
for f in $(HELM_CHART_DIR)/ci/*-values.yaml; do \
echo "==> $$(basename $$f)"; \
helm template $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) --include-crds -f "$$f" \
| $(CONFTEST) test --policy $(POLICY_DIR) - || failed=1; \
done; \
exit $$failed

##@ Build

.PHONY: build
Expand Down Expand Up @@ -162,18 +181,32 @@ KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION)
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION)
ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)
KUBE_LINTER = $(LOCALBIN)/kube-linter-$(KUBE_LINTER_VERSION)
CONFTEST = $(LOCALBIN)/conftest-$(CONFTEST_VERSION)

## Tool Versions
KUSTOMIZE_VERSION ?= v5.3.0
CONTROLLER_TOOLS_VERSION ?= v0.14.0
ENVTEST_VERSION ?= latest
GOLANGCI_LINT_VERSION ?= v1.60.1
GOLANGCI_LINT_VERSION ?= v2.4.0
KUBE_LINTER_VERSION ?= v0.6.8
CONFTEST_VERSION ?= v0.60.0

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))

.PHONY: kube-linter
kube-linter: $(KUBE_LINTER) ## Download kube-linter locally if necessary.
$(KUBE_LINTER): $(LOCALBIN)
$(call go-install-tool,$(KUBE_LINTER),golang.stackrox.io/kube-linter/cmd/kube-linter,$(KUBE_LINTER_VERSION))

.PHONY: conftest
conftest: $(CONFTEST) ## Download conftest locally if necessary.
$(CONFTEST): $(LOCALBIN)
$(call go-install-tool,$(CONFTEST),github.com/open-policy-agent/conftest,$(CONFTEST_VERSION))

.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
Expand All @@ -187,7 +220,12 @@ $(ENVTEST): $(LOCALBIN)
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})
@[ -f $(GOLANGCI_LINT) ] || { \
set -e; \
echo "Downloading golangci-lint $(GOLANGCI_LINT_VERSION)" ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh | sh -s -- -b $(LOCALBIN) $(GOLANGCI_LINT_VERSION) ;\
mv $(LOCALBIN)/golangci-lint $(GOLANGCI_LINT) ;\
}

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
Expand Down
5 changes: 2 additions & 3 deletions api/v1alpha1/issuer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,9 @@ func (is *IssuerStatus) HasCondition(conditionType IssuerConditionType, state Co
}

func (is *IssuerStatus) UnsetCondition(conditionType IssuerConditionType) {
conditions := is.Conditions
for i, c := range conditions {
for i, c := range is.Conditions {
if c.Type == conditionType {
is.Conditions = append(conditions[:i], conditions[i+1:]...)
is.Conditions = append(is.Conditions[:i], is.Conditions[i+1:]...)
return
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/issuer_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestIssuerStatus_SetCondition_UpdateConditionStatus(t *testing.T) {
assert.Equal(t, "NewMessage", cond.Message)

// LastTransitionTime should be updated because status changed from ConditionFalse -> ConditionTrue
assert.True(t, cond.LastTransitionTime.Time.After(now.Time), "LastTransitionTime should be more recent if the status changed.")
assert.True(t, cond.LastTransitionTime.After(now.Time), "LastTransitionTime should be more recent if the status changed.")
}

func TestIssuerStatus_SetCondition_NoStatusChange(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func main() {
}

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")
setupLog.Error(fmt.Errorf("interval %s is invalid, must be greater than or equal to '30s'", healthCheckInterval), "invalid health check interval")
os.Exit(1)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Cluster-wide access configuration — exercises ClusterRole/ClusterRoleBinding
# paths for secret and configmap access, and enables secure metrics.
secretConfig:
useClusterRoleForSecretAccess: true
useClusterRoleForConfigMapAccess: true
metrics:
secure: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Default configuration — all flags at their chart defaults.
# Role/RoleBinding namespace-scoped paths are exercised.
secretConfig:
useClusterRoleForSecretAccess: false
useClusterRoleForConfigMapAccess: false
metrics:
secure: false
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ rules:
- apiGroups:
- command-issuer.keyfactor.com
resources:
- clusterissuers/finalizers
- issuers/finalizers
verbs:
- update
Loading
Loading