diff --git a/Dockerfile b/Dockerfile index 2bfa558..d2b3cdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest - FROM registry.access.redhat.com/ubi9/go-toolset:9.8-1787774815 AS builder +ARG GIT_SHA=unknown +ARG GIT_DIRTY="" +ARG BUILD_DATE="" ARG APP_VERSION="0.0.0-dev" + # Install make as root (UBI9 go-toolset doesn't include it), then switch back to non-root. USER root RUN dnf install -y make && dnf clean all @@ -21,10 +24,11 @@ RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \ COPY --chown=1001:0 . . -# For FIPS-compliant builds, use CGO_ENABLED=1 + GOEXPERIMENT=boringcrypto. RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \ --mount=type=cache,target=/opt/app-root/src/.cache/go-build,uid=1001 \ - GOOS=linux make build + GOOS=linux CGO_ENABLED=0 \ + GIT_SHA=${GIT_SHA} BUILD_DATE=${BUILD_DATE} APP_VERSION=${APP_VERSION} \ + make build # Runtime stage FROM ${BASE_IMAGE} @@ -41,7 +45,6 @@ USER 65532:65532 ENTRYPOINT ["/app/hyperfleet-applier"] ARG APP_VERSION="0.0.0-dev" - LABEL name="hyperfleet-applier" \ vendor="Red Hat, Inc." \ version="${APP_VERSION}" \ diff --git a/Makefile b/Makefile index 31753ff..47206e2 100644 --- a/Makefile +++ b/Makefile @@ -17,27 +17,39 @@ LDFLAGS := -s -w \ -X main.commit=$(GIT_SHA) \ -X main.date=$(BUILD_DATE) -# Version information -CGO_ENABLED ?= 1 -GOEXPERIMENT ?= boringcrypto CONFIG ?= configs/applier.yaml KUBE_CONFIG_PATH ?= $(if $(KUBECONFIG),$(KUBECONFIG),$(HOME)/.kube/config) +# Go tool info + +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + + # Invoke a pinned tool: $(call gotool,name) # All tools share tools/go.mod with Go 1.24+ tool directives. TOOL_MOD := tools/go.mod gotool = "$(GO)" tool -modfile="$(TOOL_MOD)" $(1) +# Tool shortcuts +HELM_DOCS := $(call gotool,helm-docs) +GOLANGCI_LINT := $(call gotool,golangci-lint) +SETUP_ENVTEST := $(call gotool,setup-envtest) + + .PHONY: help help: ## Display this help @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Development +CGO_ENABLED ?= 1 + .PHONY: build -build: ## Build the applier binary - CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=$(GOEXPERIMENT) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH) ./cmd +build: ## Build the applier binary + CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH) ./cmd .PHONY: run run: build ## Run the applier service @@ -49,14 +61,16 @@ run: build ## Run the applier service test: ## Run unit tests $(GO) test -v -race -coverprofile=coverage.out ./... -.PHONY: test-envtest -test-envtest: ## Run envtest-backed integration tests against a real kube-apiserver - @assets=$$($(call gotool,setup-envtest) use -p path $(ENVTEST_K8S_VERSION)); \ - if [ -z "$$assets" ]; then \ - echo "setup-envtest: failed to resolve assets for $(ENVTEST_K8S_VERSION)"; \ - exit 1; \ - fi; \ - KUBEBUILDER_ASSETS="$$assets" $(GO) test -race -tags envtest ./... -run Envtest -v +# Removal of -i to the setup-envtest command is intentional +# If we run this envtest in CI, we want to load them from the $(PWD)/bin since we don't have access to the home directory. +# Without --bin-dir and -i setup-envtest installs the binaries in ~/.local/share/kubebuilder-envtest/ +.PHONY: setup-envtest +setup-envtest: $(LOCALBIN) ## Download the envtest binaries (etcd, kube-apiserver) into the local bin directory. + $(SETUP_ENVTEST) use '$(ENVTEST_K8S_VERSION)' --bin-dir $(LOCALBIN) -p path + +.PHONY: envtest +envtest: fmt vet setup-envtest ## Run envtest-backed integration tests against a real kube-apiserver + KUBEBUILDER_ASSETS="$$($(SETUP_ENVTEST) use '$(ENVTEST_K8S_VERSION)' --bin-dir $(LOCALBIN) -p path)" go test -race -tags envtest ./... -run Envtest -v .PHONY: fmt fmt: ## Format Go code @@ -83,7 +97,7 @@ go-vet: vet ## Alias for vet .PHONY: lint lint: ## Run golangci-lint - $(call gotool,golangci-lint) run + $(GOLANGCI_LINT) run .PHONY: verify verify: fmt-check vet helm-verify ## Run all verification checks @@ -115,7 +129,8 @@ download: ## Download dependencies # ============================================================================= # Image Configuration # ============================================================================= -IMAGE_REGISTRY ?= quay.io/openshift-hyperfleet +QUAY_REPO := openshift-hyperfleet +IMAGE_REGISTRY ?= quay.io/$(QUAY_REPO) IMAGE_NAME ?= hyperfleet-applier IMAGE_TAG ?= $(APP_VERSION) IMG ?= $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) @@ -144,11 +159,12 @@ image: check-container-tool ## Build container image with configurable registry/ @echo "Building container image $(IMG)..." $(CONTAINER_TOOL) build \ --platform $(PLATFORM) \ + --build-arg GIT_SHA=$(GIT_SHA) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ --build-arg BASE_IMAGE=$(BASE_IMAGE) \ --build-arg APP_VERSION=$(APP_VERSION) \ -t $(IMG) . @echo "Image built: $(IMG)" - @echo "$(IMG)" .PHONY: image-push image-push: check-container-tool ## Push container image to registry @@ -156,23 +172,33 @@ image-push: check-container-tool ## Push container image to registry $(CONTAINER_TOOL) push $(IMG) @echo "Image pushed: $(IMG)" +.PHONY: check-quay-user +check-quay-user: +ifeq ($(strip $(QUAY_USER)),) + @echo "Error: QUAY_USER is not set" + @echo "" + @echo "Usage: QUAY_USER=myuser make image-dev" + @exit 1 +endif # Usage: QUAY_USER=myuser make image-dev # Dev image configuration - set QUAY_USER to push to personal registry DEV_TAG ?= dev-$(GIT_SHA) QUAY_USER ?= DEV_BASE_IMAGE ?= registry.access.redhat.com/ubi9/ubi-minimal:latest + .PHONY: image-dev -image-dev: IMAGE_REGISTRY = quay.io/$(QUAY_USER) +image-dev: QUAY_REPO = $(QUAY_USER) image-dev: IMAGE_TAG = $(DEV_TAG) image-dev: BASE_IMAGE = $(DEV_BASE_IMAGE) -image-dev: check-container-tool image image-push +image-dev: check-quay-user image image-push ## Build and push dev image to dev Quay registry (requires QUAY_USER) -##@ Helm -HELM ?= helm +##@ Helm Targets + +HELM := helm +HELM_CHECK := $(shell command -v $(HELM) 2>/dev/null) CHART_DIR := charts -CHART_VALUES_FILE := charts/values.yaml # Test values for helm template rendering HELM_TEST_VALUES := \ @@ -200,16 +226,20 @@ helm-template-check: ## Verify Helm chart templates can be rendered @echo "✓ Helm chart templates rendered successfully" .PHONY: helm-verify -helm-verify: helm-lint helm-template-check verify-helm-docs ## Run all Helm chart verification checks - @echo "✓ All Helm chart checks passed" +helm-verify: ## Helm checks (helm lint, helm template, verify helm-docs) + @if [ -z "$(HELM_CHECK)" ]; then \ + echo "WARNING: helm not installed. Please install it to verify Helm chart documentation." ; \ + else \ + $(MAKE) helm-lint helm-template-check verify-helm-docs; \ + fi .PHONY: helm-docs helm-docs: ## Generate Helm chart README from values.yaml annotations - $(call gotool,helm-docs) --chart-search-root=charts --sort-values-order=file + $(HELM_DOCS) --chart-search-root=charts --sort-values-order=file .PHONY: verify-helm-docs verify-helm-docs: ## Verify chart README is up to date - $(call gotool,helm-docs) --chart-search-root=charts --sort-values-order=file + $(HELM_DOCS) --chart-search-root=charts --sort-values-order=file @git diff --exit-code charts/README.md > /dev/null 2>&1 || \ (echo "ERROR: charts/README.md is out of date. Run 'make helm-docs' and commit the result." && exit 1) diff --git a/README.md b/README.md index dd2c60e..9407889 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,16 @@ go get github.com/openshift-hyperfleet/hyperfleet-applier Credentials are stored scoped to the applier's own partition by convention. Cryptographic enforcement of that scoping arrives with the production backends. +### FIPS Compliance: + +As part of https://redhat.atlassian.net/browse/HYPERFLEET-1601 - decided to defer FIPS compliant images until compliance requirement comes in. +If want to build a FIPS compliant applier image -- add these Go variables to the `Dockerfile` and Makefile +```bash +CGO_ENABLED=1 and GOEXPERIMENT=strictfipsruntime go ... +``` + +Reference on this: https://developers.redhat.com/articles/2025/01/23/fips-mode-red-hat-go-toolset + ## Contributing 1. Verify you're a member of the `openshift-hyperfleet` organization diff --git a/charts/Chart.yaml b/charts/Chart.yaml index ea2f662..93ce096 100644 --- a/charts/Chart.yaml +++ b/charts/Chart.yaml @@ -11,3 +11,5 @@ keywords: home: https://github.com/openshift-hyperfleet/hyperfleet-applier maintainers: - name: HyperFleet Team + email: hyperfleet-team@redhat.com + url: https://github.com/openshift-hyperfleet diff --git a/charts/README.md b/charts/README.md index bea1d4a..20d64b6 100644 --- a/charts/README.md +++ b/charts/README.md @@ -10,7 +10,7 @@ HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and Delet | Name | Email | Url | | ---- | ------ | --- | -| HyperFleet Team | | | +| HyperFleet Team | | | ## Values @@ -23,7 +23,7 @@ HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and Delet | serviceAccount.annotations | object | `{}` | Annotations to add to the service account | | serviceAccount.name | string | `""` | Override the service account name | | rbac.create | bool | `true` | Create RBAC resources (ClusterRole, ClusterRoleBinding) | -| rbac.rules | list | `[{"apiGroups":["*"],"resources":["*"],"verbs":["get","list","watch","create","update","patch","delete"]}]` | ClusterRole rules - the controller needs broad permissions to apply any resource type WARNING: These broad permissions allow the applier to manage any Kubernetes resource. Future change to reduce the permissions | +| rbac.rules | list | `[{"apiGroups":["*"],"resources":["*"],"verbs":["get","list","watch","create","patch","delete"]}]` | ClusterRole rules - the controller needs broad permissions to apply any resource type. Wildcards are required because the applier dynamically manages arbitrary resource types (including CRDs) determined at runtime by desires in the Redis store. Verb breakdown by controller: ApplyDesire: create, patch (server-side apply) DeleteDesire: get, delete ReadDesire: get, list, watch (dynamic informers) "update" is intentionally excluded — the applier uses SSA (patch), never full PUT. | | podAnnotations | object | `{}` | Annotations to add to controller pods | | podLabels | object | `{}` | Labels to add to controller pods | | podSecurityContext | object | `{"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Pod-level security context | diff --git a/charts/values.yaml b/charts/values.yaml index c5c03b5..a1f5e0c 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -22,13 +22,18 @@ serviceAccount: rbac: # -- Create RBAC resources (ClusterRole, ClusterRoleBinding) create: true - # -- ClusterRole rules - the controller needs broad permissions to apply any resource type - # WARNING: These broad permissions allow the applier to manage any Kubernetes resource. - # Future change to reduce the permissions + # -- ClusterRole rules - the controller needs broad permissions to apply any resource type. + # Wildcards are required because the applier dynamically manages arbitrary resource types + # (including CRDs) determined at runtime by desires in the Redis store. + # Verb breakdown by controller: + # ApplyDesire: create, patch (server-side apply) + # DeleteDesire: get, delete + # ReadDesire: get, list, watch (dynamic informers) + # "update" is intentionally excluded — the applier uses SSA (patch), never full PUT. rules: - apiGroups: ["*"] resources: ["*"] - verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + verbs: ["get", "list", "watch", "create", "patch", "delete"] # -- Annotations to add to controller pods podAnnotations: {}