Skip to content

Commit 5d76782

Browse files
committed
CM-716: Add HTTP01 Challenge Proxy controller
Adds the HTTP01 Challenge Proxy controller to cert-manager-operator, enabling cert-manager to complete HTTP01 ACME challenges for the API endpoint on baremetal platforms where the API VIP is not exposed via OpenShift Ingress. When a user creates an HTTP01Proxy CR with mode DefaultDeployment, the operator validates the platform is BareMetal with distinct API and Ingress VIPs, then deploys a DaemonSet on control plane nodes running a reverse proxy. The proxy uses nftables to redirect HTTP traffic from API_VIP:80 to a local proxy port (default 8888). Only /.well-known/acme-challenge/* requests are forwarded to the Ingress VIP; all other requests are rejected. Feature gate: HTTP01Proxy (Alpha, disabled by default) Enable via: --unsupported-addon-features=HTTP01Proxy=true Jira: https://issues.redhat.com/browse/CM-716 Enhancement: openshift/enhancements#1929
1 parent 2beb5f9 commit 5d76782

3,530 files changed

Lines changed: 182847 additions & 183000 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci-operator.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
build_root_image:
22
name: builder
33
namespace: ocp
4-
tag: rhel-9-golang-1.25-openshift-4.21
4+
tag: rhel-9-golang-1.26-openshift-4.23

.golangci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ linters:
130130
linters:
131131
- godot
132132
# Exclude false positive hardcoded credential warnings (these are paths, not credentials)
133-
- path: pkg/controller/deployment/credentials_request.go
133+
- path: pkg/controller/certmanager/credentials_request.go
134134
text: "gcpCredentialsDir|cloudCredentialsVolumeName"
135135
linters:
136136
- gosec
137-
- path: pkg/controller/deployment/deployment_overrides.go
137+
- path: pkg/controller/certmanager/deployment_overrides.go
138138
text: "boundSATokenDir"
139139
linters:
140140
- gosec
@@ -150,7 +150,7 @@ linters:
150150
# Exclude unparam for functions that may be part of interfaces
151151
- linters:
152152
- unparam
153-
path: pkg/controller/deployment/
153+
path: pkg/controller/certmanager/
154154
settings:
155155
wrapcheck:
156156
# Ignore errors returned from these packages - they are either internal or

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/golang:1.25 AS builder
1+
FROM docker.io/golang:1.26 AS builder
22
WORKDIR /go/src/github.com/openshift/cert-manager-operator
33
COPY . .
44
RUN make build --warn-undefined-variables

Makefile

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export XDG_CONFIG_HOME ?= $(PROJECT_ROOT)/_output/.config
1919
# ============================================================================
2020

2121
# DEFAULT_VERSION is the default version to use for image tags when not set.
22-
DEFAULT_VERSION := 1.19.0
22+
DEFAULT_VERSION := 1.20.0
2323

2424
# Helper function to validate semver (Major.Minor.Patch format)
2525
# Returns 'valid' if the version matches semver (X.Y.Z) or 'latest', empty string otherwise
@@ -57,12 +57,17 @@ endif
5757
# --- Operand Versions ---
5858

5959
# Versions of the cert-manager components managed by this operator
60-
CERT_MANAGER_VERSION ?= v1.19.4
60+
CERT_MANAGER_VERSION ?= v1.20.2
6161
ISTIO_CSR_VERSION ?= v0.16.0
6262
TRUST_MANAGER_VERSION ?= v0.20.3
6363

6464
# --- Test Versions ---
6565

66+
# OpenShift Service Mesh versions for IstioCSR ServiceMesh e2e tests.
67+
# Keep servicemesh_helpers_test.go ossmDefault* constants in sync when bumping.
68+
E2E_OSM_ISTIO_VERSION ?= v1.24.3
69+
E2E_OSM_OPERATOR_VERSION ?= 3.2.5
70+
6671
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
6772
ENVTEST_K8S_VERSION ?= 1.32.0
6873

@@ -132,7 +137,7 @@ USE_IMAGE_DIGESTS ?= false
132137

133138
# CHANNELS define the bundle channels used in the bundle.
134139
# To override: make bundle CHANNELS=candidate,fast,stable or export CHANNELS="candidate,fast,stable"
135-
CHANNELS ?= stable-v1,stable-v1.19
140+
CHANNELS ?= stable-v1,stable-v1.20
136141
ifneq ($(origin CHANNELS), undefined)
137142
BUNDLE_CHANNELS := --channels=$(CHANNELS)
138143
endif
@@ -164,7 +169,7 @@ TLS_VERIFY ?= true
164169
CONTAINER_PUSH_ARGS ?= $(if $(filter $(CONTAINER_ENGINE),docker),,--tls-verify=$(TLS_VERIFY))
165170

166171
# Container image used for running make targets in a container
167-
CONTAINER_IMAGE_NAME ?= registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.21
172+
CONTAINER_IMAGE_NAME ?= registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.26-openshift-4.23
168173

169174
# ============================================================================
170175
# Build Configuration
@@ -188,7 +193,7 @@ E2E_TIMEOUT ?= 2h
188193
# E2E_GINKGO_LABEL_FILTER is ginkgo label query for selecting tests.
189194
# See https://onsi.github.io/ginkgo/#spec-labels
190195
# The default is to run tests on the AWS platform.
191-
E2E_GINKGO_LABEL_FILTER ?= Platform: isSubsetOf {AWS,Generic} && CredentialsMode: isSubsetOf {Mint}
196+
E2E_GINKGO_LABEL_FILTER ?= Platform: isSubsetOf {AWS,Generic} && CredentialsMode: isSubsetOf {Mint} && !Feature:ServiceMesh
192197

193198
# ============================================================================
194199
# Default Target
@@ -279,6 +284,8 @@ test-apis: $(SETUP_ENVTEST) $(GINKGO)
279284
TEST ?=
280285
.PHONY: test-e2e
281286
test-e2e: test-e2e-wait-for-stable-state ## Run end-to-end tests.
287+
E2E_OSM_ISTIO_VERSION=$(E2E_OSM_ISTIO_VERSION) \
288+
E2E_OSM_OPERATOR_VERSION=$(E2E_OSM_OPERATOR_VERSION) \
282289
go test -C $(PROJECT_ROOT)/test/e2e \
283290
-timeout $(E2E_TIMEOUT) \
284291
-count 1 -v -p 1 \
@@ -322,10 +329,12 @@ local-run: build ## Run the operator locally against the cluster configured in ~
322329
RELATED_IMAGE_CERT_MANAGER_ACMESOLVER=quay.io/jetstack/cert-manager-acmesolver:$(CERT_MANAGER_VERSION) \
323330
RELATED_IMAGE_CERT_MANAGER_ISTIOCSR=quay.io/jetstack/cert-manager-istio-csr:$(ISTIO_CSR_VERSION) \
324331
RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER=quay.io/jetstack/trust-manager:$(TRUST_MANAGER_VERSION) \
332+
RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY=quay.io/bapalm/cert-mgr-http01-proxy:latest \
325333
OPERATOR_NAME=cert-manager-operator \
326334
OPERAND_IMAGE_VERSION=$(BUNDLE_VERSION) \
327335
ISTIOCSR_OPERAND_IMAGE_VERSION=$(ISTIO_CSR_VERSION) \
328336
TRUSTMANAGER_OPERAND_IMAGE_VERSION=$(TRUST_MANAGER_VERSION) \
337+
HTTP01PROXY_OPERAND_IMAGE_VERSION=0.1.0 \
329338
OPERATOR_IMAGE_VERSION=$(BUNDLE_VERSION) \
330339
./cert-manager-operator start \
331340
--config=./hack/local-run-config.yaml \
@@ -436,6 +445,37 @@ verify-with-container: ## Run verification in a container.
436445
govulncheck: $(GOVULNCHECK) $(OUTPUT_DIR) ## Run govulncheck vulnerability scan.
437446
@./hack/govulncheck.sh $(GOVULNCHECK) $(OUTPUT_DIR)
438447

448+
# ============================================================================
449+
# E2E Coverage
450+
# ============================================================================
451+
452+
##@ E2E Coverage
453+
##
454+
## Targets for building a coverage-instrumented operator image, collecting
455+
## coverage data written during E2E tests, and uploading the report to Codecov.
456+
##
457+
## Typical flow (local):
458+
## make image-build-coverage image-push-coverage # build & push coverage image
459+
## COVERAGE_IMAGE=<pullspec> hack/e2e-coverage.sh setup # patch CSV
460+
## make test-e2e # run E2E suite
461+
## make e2e-coverage-collect # collect + upload
462+
##
463+
## In CI, hack/e2e-coverage.sh handles setup and collection automatically.
464+
465+
COVERAGE_IMG ?= $(IMG)-e2e-coverage
466+
467+
.PHONY: image-build-coverage
468+
image-build-coverage: ## Build coverage-instrumented container image.
469+
$(CONTAINER_ENGINE) build -f images/ci/Dockerfile.coverage -t $(COVERAGE_IMG) .
470+
471+
.PHONY: image-push-coverage
472+
image-push-coverage: ## Push coverage-instrumented container image.
473+
$(CONTAINER_ENGINE) push $(COVERAGE_IMG) $(CONTAINER_PUSH_ARGS)
474+
475+
.PHONY: e2e-coverage-collect
476+
e2e-coverage-collect: ## Collect e2e coverage data and optionally upload to Codecov.
477+
ARTIFACT_DIR=$${ARTIFACT_DIR:-.} hack/e2e-coverage.sh collect
478+
439479
# ============================================================================
440480
# Maintenance
441481
# ============================================================================
@@ -559,4 +599,4 @@ $(OPERATOR_SDK): ## Download operator-sdk locally if necessary.
559599
hack/download-tools.sh operator-sdk $(OPERATOR_SDK)
560600

561601
$(OPM): ## Download opm locally if necessary.
562-
hack/download-tools.sh opm $(OPM)
602+
hack/download-tools.sh opm $(OPM)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This repository contains Cert Manager Operator designed for OpenShift. The opera
55
## The operator architecture and design assumptions
66

77
The Operator uses the [upstream deployment manifests](https://github.com/cert-manager/cert-manager/releases). It divides them into separate files and deploys using 3 controllers:
8-
- [cert_manager_cainjector_deployment.go](pkg/controller/deployment/cert_manager_cainjector_deployment.go)
9-
- [cert_manager_controller_deployment.go](pkg/controller/deployment/cert_manager_controller_deployment.go)
10-
- [cert_manager_webhook_deployment.go](pkg/controller/deployment/cert_manager_webhook_deployment.go)
8+
- [cert_manager_cainjector_deployment.go](pkg/controller/certmanager/cert_manager_cainjector_deployment.go)
9+
- [cert_manager_controller_deployment.go](pkg/controller/certmanager/cert_manager_controller_deployment.go)
10+
- [cert_manager_webhook_deployment.go](pkg/controller/certmanager/cert_manager_webhook_deployment.go)
1111

1212
The Operator automatically deploys a cluster-scoped `CertManager` object named `cluster` if it's missing (with default values).
1313

api/operator/v1alpha1/features.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,19 @@ var (
2121
// For more details,
2222
// https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/trust-manager-controller.md
2323
FeatureTrustManager featuregate.Feature = "TrustManager"
24+
25+
// HTTP01Proxy enables the controller for http01proxies.operator.openshift.io resource,
26+
// which extends cert-manager-operator to deploy and manage the HTTP01 challenge proxy.
27+
// The proxy enables cert-manager to complete HTTP01 ACME challenges for the API endpoint
28+
// on baremetal platforms where the API VIP is not exposed via OpenShift Ingress.
29+
//
30+
// For more details,
31+
// https://github.com/openshift/enhancements/pull/1929
32+
FeatureHTTP01Proxy featuregate.Feature = "HTTP01Proxy"
2433
)
2534

2635
var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
2736
FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA},
2837
FeatureTrustManager: {Default: false, PreRelease: "TechPreview"},
38+
FeatureHTTP01Proxy: {Default: false, PreRelease: featuregate.Alpha},
2939
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package v1alpha1
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
func init() {
8+
SchemeBuilder.Register(&HTTP01Proxy{}, &HTTP01ProxyList{})
9+
}
10+
11+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
12+
// +kubebuilder:object:root=true
13+
14+
// HTTP01ProxyList is a list of HTTP01Proxy objects.
15+
type HTTP01ProxyList struct {
16+
metav1.TypeMeta `json:",inline"`
17+
18+
// metadata is the standard list's metadata.
19+
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
20+
metav1.ListMeta `json:"metadata"`
21+
Items []HTTP01Proxy `json:"items"`
22+
}
23+
24+
// +genclient
25+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
26+
// +kubebuilder:object:root=true
27+
// +kubebuilder:subresource:status
28+
// +kubebuilder:resource:path=http01proxies,scope=Namespaced,categories={cert-manager-operator},shortName=http01proxy
29+
// +kubebuilder:printcolumn:name="Mode",type="string",JSONPath=".spec.mode"
30+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
31+
// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message"
32+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
33+
// +kubebuilder:metadata:labels={"app.kubernetes.io/name=http01proxy", "app.kubernetes.io/part-of=cert-manager-operator"}
34+
35+
// HTTP01Proxy describes the configuration for the HTTP01 challenge proxy
36+
// that redirects traffic from the API endpoint on port 80 to ingress routers.
37+
// This enables cert-manager to perform HTTP01 ACME challenges for API endpoint certificates.
38+
// The name must be `default` to make HTTP01Proxy a singleton.
39+
//
40+
// When an HTTP01Proxy is created, the proxy DaemonSet is deployed on control plane nodes.
41+
//
42+
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default'",message="http01proxy is a singleton, .metadata.name must be 'default'"
43+
// +operator-sdk:csv:customresourcedefinitions:displayName="HTTP01Proxy"
44+
type HTTP01Proxy struct {
45+
metav1.TypeMeta `json:",inline"`
46+
47+
// metadata is the standard object's metadata.
48+
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
49+
metav1.ObjectMeta `json:"metadata,omitempty"`
50+
51+
// spec is the specification of the desired behavior of the HTTP01Proxy.
52+
// +kubebuilder:validation:Required
53+
// +required
54+
Spec HTTP01ProxySpec `json:"spec"`
55+
56+
// status is the most recently observed status of the HTTP01Proxy.
57+
// +kubebuilder:validation:Optional
58+
// +optional
59+
Status HTTP01ProxyStatus `json:"status,omitempty"`
60+
}
61+
62+
// HTTP01ProxyMode controls how the HTTP01 challenge proxy is deployed.
63+
// +kubebuilder:validation:Enum=DefaultDeployment;CustomDeployment
64+
type HTTP01ProxyMode string
65+
66+
const (
67+
// HTTP01ProxyModeDefault enables the proxy with default configuration.
68+
HTTP01ProxyModeDefault HTTP01ProxyMode = "DefaultDeployment"
69+
70+
// HTTP01ProxyModeCustom enables the proxy with user-specified configuration.
71+
HTTP01ProxyModeCustom HTTP01ProxyMode = "CustomDeployment"
72+
)
73+
74+
// HTTP01ProxySpec is the specification of the desired behavior of the HTTP01Proxy.
75+
// +kubebuilder:validation:XValidation:rule="self.mode == 'CustomDeployment' ? has(self.customDeployment) : !has(self.customDeployment)",message="customDeployment is required when mode is CustomDeployment and forbidden otherwise"
76+
type HTTP01ProxySpec struct {
77+
// mode controls whether the HTTP01 challenge proxy is active and how it should be deployed.
78+
// DefaultDeployment enables the proxy with default configuration.
79+
// CustomDeployment enables the proxy with user-specified configuration.
80+
// +kubebuilder:validation:Required
81+
// +required
82+
Mode HTTP01ProxyMode `json:"mode"`
83+
84+
// customDeployment contains configuration options when mode is CustomDeployment.
85+
// This field is only valid when mode is CustomDeployment.
86+
// +kubebuilder:validation:Optional
87+
// +optional
88+
CustomDeployment *HTTP01ProxyCustomDeploymentSpec `json:"customDeployment,omitempty"`
89+
}
90+
91+
// HTTP01ProxyCustomDeploymentSpec contains configuration for custom proxy deployment.
92+
type HTTP01ProxyCustomDeploymentSpec struct {
93+
// internalPort specifies the internal port used by the proxy service.
94+
// Valid values are 1024-65535.
95+
// +kubebuilder:validation:Minimum=1024
96+
// +kubebuilder:validation:Maximum=65535
97+
// +kubebuilder:default=8888
98+
// +optional
99+
InternalPort int32 `json:"internalPort,omitempty"`
100+
}
101+
102+
// HTTP01ProxyStatus is the most recently observed status of the HTTP01Proxy.
103+
type HTTP01ProxyStatus struct {
104+
// conditions holds information about the current state of the HTTP01 proxy deployment.
105+
ConditionalStatus `json:",inline,omitempty"`
106+
107+
// proxyImage is the name of the image and the tag used for deploying the proxy.
108+
ProxyImage string `json:"proxyImage,omitempty"`
109+
}

0 commit comments

Comments
 (0)