diff --git a/Makefile b/Makefile index 0caec65d0..58087080b 100644 --- a/Makefile +++ b/Makefile @@ -322,10 +322,12 @@ local-run: build ## Run the operator locally against the cluster configured in ~ RELATED_IMAGE_CERT_MANAGER_ACMESOLVER=quay.io/jetstack/cert-manager-acmesolver:$(CERT_MANAGER_VERSION) \ RELATED_IMAGE_CERT_MANAGER_ISTIOCSR=quay.io/jetstack/cert-manager-istio-csr:$(ISTIO_CSR_VERSION) \ RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER=quay.io/jetstack/trust-manager:$(TRUST_MANAGER_VERSION) \ + RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY=quay.io/bapalm/cert-mgr-http01-proxy:latest \ OPERATOR_NAME=cert-manager-operator \ OPERAND_IMAGE_VERSION=$(BUNDLE_VERSION) \ ISTIOCSR_OPERAND_IMAGE_VERSION=$(ISTIO_CSR_VERSION) \ TRUSTMANAGER_OPERAND_IMAGE_VERSION=$(TRUST_MANAGER_VERSION) \ + HTTP01PROXY_OPERAND_IMAGE_VERSION=0.1.0 \ OPERATOR_IMAGE_VERSION=$(BUNDLE_VERSION) \ ./cert-manager-operator start \ --config=./hack/local-run-config.yaml \ diff --git a/api/operator/v1alpha1/features.go b/api/operator/v1alpha1/features.go index 6d9c380a5..bdaad7f1f 100644 --- a/api/operator/v1alpha1/features.go +++ b/api/operator/v1alpha1/features.go @@ -21,9 +21,19 @@ var ( // For more details, // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/trust-manager-controller.md FeatureTrustManager featuregate.Feature = "TrustManager" + + // HTTP01Proxy enables the controller for http01proxies.operator.openshift.io resource, + // which extends cert-manager-operator to deploy and manage the HTTP01 challenge proxy. + // The proxy enables cert-manager to complete HTTP01 ACME challenges for the API endpoint + // on baremetal platforms where the API VIP is not exposed via OpenShift Ingress. + // + // For more details, + // https://github.com/openshift/enhancements/pull/1929 + FeatureHTTP01Proxy featuregate.Feature = "HTTP01Proxy" ) var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, FeatureTrustManager: {Default: false, PreRelease: "TechPreview"}, + FeatureHTTP01Proxy: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/api/operator/v1alpha1/http01proxy_types.go b/api/operator/v1alpha1/http01proxy_types.go new file mode 100644 index 000000000..742d507ba --- /dev/null +++ b/api/operator/v1alpha1/http01proxy_types.go @@ -0,0 +1,109 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func init() { + SchemeBuilder.Register(&HTTP01Proxy{}, &HTTP01ProxyList{}) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true + +// HTTP01ProxyList is a list of HTTP01Proxy objects. +type HTTP01ProxyList struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard list's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ListMeta `json:"metadata"` + Items []HTTP01Proxy `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=http01proxies,scope=Namespaced,categories={cert-manager-operator},shortName=http01proxy +// +kubebuilder:printcolumn:name="Mode",type="string",JSONPath=".spec.mode" +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:metadata:labels={"app.kubernetes.io/name=http01proxy", "app.kubernetes.io/part-of=cert-manager-operator"} + +// HTTP01Proxy describes the configuration for the HTTP01 challenge proxy +// that redirects traffic from the API endpoint on port 80 to ingress routers. +// This enables cert-manager to perform HTTP01 ACME challenges for API endpoint certificates. +// The name must be `default` to make HTTP01Proxy a singleton. +// +// When an HTTP01Proxy is created, the proxy DaemonSet is deployed on control plane nodes. +// +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default'",message="http01proxy is a singleton, .metadata.name must be 'default'" +// +operator-sdk:csv:customresourcedefinitions:displayName="HTTP01Proxy" +type HTTP01Proxy struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is the specification of the desired behavior of the HTTP01Proxy. + // +kubebuilder:validation:Required + // +required + Spec HTTP01ProxySpec `json:"spec"` + + // status is the most recently observed status of the HTTP01Proxy. + // +kubebuilder:validation:Optional + // +optional + Status HTTP01ProxyStatus `json:"status,omitempty"` +} + +// HTTP01ProxyMode controls how the HTTP01 challenge proxy is deployed. +// +kubebuilder:validation:Enum=DefaultDeployment;CustomDeployment +type HTTP01ProxyMode string + +const ( + // HTTP01ProxyModeDefault enables the proxy with default configuration. + HTTP01ProxyModeDefault HTTP01ProxyMode = "DefaultDeployment" + + // HTTP01ProxyModeCustom enables the proxy with user-specified configuration. + HTTP01ProxyModeCustom HTTP01ProxyMode = "CustomDeployment" +) + +// HTTP01ProxySpec is the specification of the desired behavior of the HTTP01Proxy. +// +kubebuilder:validation:XValidation:rule="self.mode == 'CustomDeployment' ? has(self.customDeployment) : !has(self.customDeployment)",message="customDeployment is required when mode is CustomDeployment and forbidden otherwise" +type HTTP01ProxySpec struct { + // mode controls whether the HTTP01 challenge proxy is active and how it should be deployed. + // DefaultDeployment enables the proxy with default configuration. + // CustomDeployment enables the proxy with user-specified configuration. + // +kubebuilder:validation:Required + // +required + Mode HTTP01ProxyMode `json:"mode"` + + // customDeployment contains configuration options when mode is CustomDeployment. + // This field is only valid when mode is CustomDeployment. + // +kubebuilder:validation:Optional + // +optional + CustomDeployment *HTTP01ProxyCustomDeploymentSpec `json:"customDeployment,omitempty"` +} + +// HTTP01ProxyCustomDeploymentSpec contains configuration for custom proxy deployment. +type HTTP01ProxyCustomDeploymentSpec struct { + // internalPort specifies the internal port used by the proxy service. + // Valid values are 1024-65535. + // +kubebuilder:validation:Minimum=1024 + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:default=8888 + // +optional + InternalPort int32 `json:"internalPort,omitempty"` +} + +// HTTP01ProxyStatus is the most recently observed status of the HTTP01Proxy. +type HTTP01ProxyStatus struct { + // conditions holds information about the current state of the HTTP01 proxy deployment. + ConditionalStatus `json:",inline,omitempty"` + + // proxyImage is the name of the image and the tag used for deploying the proxy. + ProxyImage string `json:"proxyImage,omitempty"` +} diff --git a/api/operator/v1alpha1/zz_generated.deepcopy.go b/api/operator/v1alpha1/zz_generated.deepcopy.go index 883cddf1b..a0d015685 100644 --- a/api/operator/v1alpha1/zz_generated.deepcopy.go +++ b/api/operator/v1alpha1/zz_generated.deepcopy.go @@ -334,6 +334,116 @@ func (in *DeploymentConfig) DeepCopy() *DeploymentConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP01Proxy) DeepCopyInto(out *HTTP01Proxy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP01Proxy. +func (in *HTTP01Proxy) DeepCopy() *HTTP01Proxy { + if in == nil { + return nil + } + out := new(HTTP01Proxy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HTTP01Proxy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP01ProxyCustomDeploymentSpec) DeepCopyInto(out *HTTP01ProxyCustomDeploymentSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP01ProxyCustomDeploymentSpec. +func (in *HTTP01ProxyCustomDeploymentSpec) DeepCopy() *HTTP01ProxyCustomDeploymentSpec { + if in == nil { + return nil + } + out := new(HTTP01ProxyCustomDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP01ProxyList) DeepCopyInto(out *HTTP01ProxyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HTTP01Proxy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP01ProxyList. +func (in *HTTP01ProxyList) DeepCopy() *HTTP01ProxyList { + if in == nil { + return nil + } + out := new(HTTP01ProxyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HTTP01ProxyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP01ProxySpec) DeepCopyInto(out *HTTP01ProxySpec) { + *out = *in + if in.CustomDeployment != nil { + in, out := &in.CustomDeployment, &out.CustomDeployment + *out = new(HTTP01ProxyCustomDeploymentSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP01ProxySpec. +func (in *HTTP01ProxySpec) DeepCopy() *HTTP01ProxySpec { + if in == nil { + return nil + } + out := new(HTTP01ProxySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTP01ProxyStatus) DeepCopyInto(out *HTTP01ProxyStatus) { + *out = *in + in.ConditionalStatus.DeepCopyInto(&out.ConditionalStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP01ProxyStatus. +func (in *HTTP01ProxyStatus) DeepCopy() *HTTP01ProxyStatus { + if in == nil { + return nil + } + out := new(HTTP01ProxyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IstioCSR) DeepCopyInto(out *IstioCSR) { *out = *in diff --git a/bindata/http01-proxy/cert-manager-http01-proxy-clusterrole.yaml b/bindata/http01-proxy/cert-manager-http01-proxy-clusterrole.yaml new file mode 100644 index 000000000..f53a0c27d --- /dev/null +++ b/bindata/http01-proxy/cert-manager-http01-proxy-clusterrole.yaml @@ -0,0 +1,34 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-http01-proxy + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - config.openshift.io + resources: + - clusterversions + - infrastructures + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - operator.openshift.io + resources: + - machineconfigurations + verbs: + - update + - apiGroups: + - machineconfiguration.openshift.io + resources: + - machineconfigs + verbs: + - get + - list + - create + - update diff --git a/bindata/http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml b/bindata/http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml new file mode 100644 index 000000000..ceda98164 --- /dev/null +++ b/bindata/http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-http01-proxy + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-http01-proxy +subjects: + - kind: ServiceAccount + name: cert-manager-http01-proxy + namespace: cert-manager-operator diff --git a/bindata/http01-proxy/cert-manager-http01-proxy-daemonset.yaml b/bindata/http01-proxy/cert-manager-http01-proxy-daemonset.yaml new file mode 100644 index 000000000..808933bf7 --- /dev/null +++ b/bindata/http01-proxy/cert-manager-http01-proxy-daemonset.yaml @@ -0,0 +1,60 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: cert-manager-http01-proxy + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + selector: + matchLabels: + app: cert-manager-http01-proxy + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator + spec: + serviceAccountName: cert-manager-http01-proxy + hostNetwork: true + nodeSelector: + node-role.kubernetes.io/master: "" + tolerations: + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + containers: + - name: http01-proxy + image: ${RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY} + ports: + - name: proxy + containerPort: 8888 + hostPort: 8888 + protocol: TCP + env: + - name: PROXY_PORT + value: "8888" + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + drop: + - ALL + runAsNonRoot: false + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + priorityClassName: system-cluster-critical diff --git a/bindata/http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml b/bindata/http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml new file mode 100644 index 000000000..0a5603031 --- /dev/null +++ b/bindata/http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-http01-proxy-scc + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: cert-manager-http01-proxy + namespace: cert-manager-operator diff --git a/bindata/http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml b/bindata/http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml new file mode 100644 index 000000000..a738b4a01 --- /dev/null +++ b/bindata/http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cert-manager-http01-proxy + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator diff --git a/bindata/networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml b/bindata/networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml new file mode 100644 index 000000000..48a6c9b07 --- /dev/null +++ b/bindata/networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cert-manager-http01-proxy-allow-egress + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + podSelector: + matchLabels: + app: cert-manager-http01-proxy + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + - port: 80 + protocol: TCP diff --git a/bindata/networkpolicies/http01-proxy-deny-all-networkpolicy.yaml b/bindata/networkpolicies/http01-proxy-deny-all-networkpolicy.yaml new file mode 100644 index 000000000..7b4c8c1ba --- /dev/null +++ b/bindata/networkpolicies/http01-proxy-deny-all-networkpolicy.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cert-manager-http01-proxy-deny-all + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + podSelector: + matchLabels: + app: cert-manager-http01-proxy + policyTypes: + - Ingress + - Egress diff --git a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml index cc06245af..ee8a92a9c 100644 --- a/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cert-manager-operator.clusterserviceversion.yaml @@ -495,6 +495,7 @@ spec: - apiGroups: - apps resources: + - daemonsets - deployments - replicasets verbs: @@ -634,6 +635,7 @@ spec: - operator.openshift.io resources: - certmanagers/finalizers + - http01proxies/finalizers - istiocsrs/finalizers - trustmanagers/finalizers verbs: @@ -642,6 +644,7 @@ spec: - operator.openshift.io resources: - certmanagers/status + - http01proxies/status - istiocsrs/status - trustmanagers/status verbs: @@ -651,6 +654,7 @@ spec: - apiGroups: - operator.openshift.io resources: + - http01proxies - istiocsrs - trustmanagers verbs: @@ -790,12 +794,16 @@ spec: value: quay.io/jetstack/cert-manager-istio-csr:v0.16.0 - name: RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER value: quay.io/jetstack/trust-manager:v0.20.3 + - name: RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY + value: quay.io/bapalm/cert-mgr-http01-proxy:latest - name: OPERAND_IMAGE_VERSION value: 1.19.4 - name: ISTIOCSR_OPERAND_IMAGE_VERSION value: 0.16.0 - name: TRUSTMANAGER_OPERAND_IMAGE_VERSION value: 0.20.3 + - name: HTTP01PROXY_OPERAND_IMAGE_VERSION + value: 0.1.0 - name: OPERATOR_IMAGE_VERSION value: 1.19.0 - name: OPERATOR_LOG_LEVEL @@ -912,5 +920,7 @@ spec: name: cert-manager-istiocsr - image: quay.io/jetstack/trust-manager:v0.20.3 name: cert-manager-trust-manager + - image: quay.io/bapalm/cert-mgr-http01-proxy:latest + name: cert-manager-http01proxy replaces: cert-manager-operator.v1.18.0 version: 1.19.0 diff --git a/config/crd/bases/operator.openshift.io_http01proxies.yaml b/config/crd/bases/operator.openshift.io_http01proxies.yaml new file mode 100644 index 000000000..98bf30e8f --- /dev/null +++ b/config/crd/bases/operator.openshift.io_http01proxies.yaml @@ -0,0 +1,179 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + labels: + app.kubernetes.io/name: http01proxy + app.kubernetes.io/part-of: cert-manager-operator + name: http01proxies.operator.openshift.io +spec: + group: operator.openshift.io + names: + categories: + - cert-manager-operator + kind: HTTP01Proxy + listKind: HTTP01ProxyList + plural: http01proxies + shortNames: + - http01proxy + singular: http01proxy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.mode + name: Mode + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + HTTP01Proxy describes the configuration for the HTTP01 challenge proxy + that redirects traffic from the API endpoint on port 80 to ingress routers. + This enables cert-manager to perform HTTP01 ACME challenges for API endpoint certificates. + The name must be `default` to make HTTP01Proxy a singleton. + + When an HTTP01Proxy is created, the proxy DaemonSet is deployed on control plane nodes. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is the specification of the desired behavior of the + HTTP01Proxy. + properties: + customDeployment: + description: |- + customDeployment contains configuration options when mode is CustomDeployment. + This field is only valid when mode is CustomDeployment. + properties: + internalPort: + default: 8888 + description: |- + internalPort specifies the internal port used by the proxy service. + Valid values are 1024-65535. + format: int32 + maximum: 65535 + minimum: 1024 + type: integer + type: object + mode: + description: |- + mode controls whether the HTTP01 challenge proxy is active and how it should be deployed. + DefaultDeployment enables the proxy with default configuration. + CustomDeployment enables the proxy with user-specified configuration. + enum: + - DefaultDeployment + - CustomDeployment + type: string + required: + - mode + type: object + x-kubernetes-validations: + - message: customDeployment is required when mode is CustomDeployment + and forbidden otherwise + rule: 'self.mode == ''CustomDeployment'' ? has(self.customDeployment) + : !has(self.customDeployment)' + status: + description: status is the most recently observed status of the HTTP01Proxy. + properties: + conditions: + description: conditions holds information about the current state + of the operand deployment. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + proxyImage: + description: proxyImage is the name of the image and the tag used + for deploying the proxy. + type: string + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: http01proxy is a singleton, .metadata.name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index a19ad2cf8..dd5a0b52d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -86,12 +86,16 @@ spec: value: quay.io/jetstack/cert-manager-istio-csr:v0.16.0 - name: RELATED_IMAGE_CERT_MANAGER_TRUST_MANAGER value: quay.io/jetstack/trust-manager:v0.20.3 + - name: RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY + value: quay.io/bapalm/cert-mgr-http01-proxy:latest - name: OPERAND_IMAGE_VERSION value: 1.19.4 - name: ISTIOCSR_OPERAND_IMAGE_VERSION value: 0.16.0 - name: TRUSTMANAGER_OPERAND_IMAGE_VERSION value: 0.20.3 + - name: HTTP01PROXY_OPERAND_IMAGE_VERSION + value: 0.1.0 - name: OPERATOR_IMAGE_VERSION value: 1.19.0 - name: OPERATOR_LOG_LEVEL diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ed4472e77..77cbd9d8a 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -86,6 +86,7 @@ rules: - apiGroups: - apps resources: + - daemonsets - deployments - replicasets verbs: @@ -225,6 +226,7 @@ rules: - operator.openshift.io resources: - certmanagers/finalizers + - http01proxies/finalizers - istiocsrs/finalizers - trustmanagers/finalizers verbs: @@ -233,6 +235,7 @@ rules: - operator.openshift.io resources: - certmanagers/status + - http01proxies/status - istiocsrs/status - trustmanagers/status verbs: @@ -242,6 +245,7 @@ rules: - apiGroups: - operator.openshift.io resources: + - http01proxies - istiocsrs - trustmanagers verbs: diff --git a/config/samples/operator.openshift.io_v1alpha1_http01proxy.yaml b/config/samples/operator.openshift.io_v1alpha1_http01proxy.yaml new file mode 100644 index 000000000..d5988f361 --- /dev/null +++ b/config/samples/operator.openshift.io_v1alpha1_http01proxy.yaml @@ -0,0 +1,7 @@ +apiVersion: operator.openshift.io/v1alpha1 +kind: HTTP01Proxy +metadata: + name: default + namespace: cert-manager-operator +spec: + mode: DefaultDeployment diff --git a/pkg/controller/http01proxy/constants.go b/pkg/controller/http01proxy/constants.go new file mode 100644 index 000000000..ad58450b3 --- /dev/null +++ b/pkg/controller/http01proxy/constants.go @@ -0,0 +1,77 @@ +package http01proxy + +import ( + "os" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +var ( + // infrastructureGVK is the GVK for the OpenShift Infrastructure resource. + infrastructureGVK = schema.GroupVersionKind{ + Group: "config.openshift.io", + Version: "v1", + Kind: "Infrastructure", + } +) + +const ( + // http01proxyCommonName is the name commonly used for naming resources. + http01proxyCommonName = "cert-manager-http01-proxy" + + // ControllerName is the name of the controller used in logs and events. + ControllerName = http01proxyCommonName + "-controller" + + // controllerProcessedAnnotation is the annotation added to http01proxy resource once after + // successful reconciliation by the controller. + controllerProcessedAnnotation = "operator.openshift.io/http01-proxy-processed" + + // finalizer name for http01proxies.operator.openshift.io resource. + finalizer = "http01proxy.openshift.operator.io/" + ControllerName + + // defaultRequeueTime is the default reconcile requeue time. + defaultRequeueTime = time.Second * 30 + + // http01proxyObjectName is the name of the http01proxy resource created by user. + // The CRD enforces name to be `default`. + http01proxyObjectName = "default" + + // http01proxyImageNameEnvVarName is the environment variable key name + // containing the image name of the http01proxy as value. + http01proxyImageNameEnvVarName = "RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY" + + // http01proxyImageVersionEnvVarName is the environment variable key name + // containing the image version of the http01proxy as value. + http01proxyImageVersionEnvVarName = "HTTP01PROXY_OPERAND_IMAGE_VERSION" + + // defaultInternalPort is the default port the proxy listens on. + defaultInternalPort int32 = 8888 +) + +var ( + controllerDefaultResourceLabels = map[string]string{ + common.ManagedResourceLabelKey: http01proxyCommonName, + "app.kubernetes.io/name": http01proxyCommonName, + "app.kubernetes.io/instance": http01proxyCommonName, + "app.kubernetes.io/version": os.Getenv(http01proxyImageVersionEnvVarName), + "app.kubernetes.io/managed-by": "cert-manager-operator", + "app.kubernetes.io/part-of": "cert-manager-operator", + } +) + +// asset names are the files present in the root bindata/ dir. +const ( + daemonsetAssetName = "http01-proxy/cert-manager-http01-proxy-daemonset.yaml" + serviceAccountAssetName = "http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml" + clusterRoleAssetName = "http01-proxy/cert-manager-http01-proxy-clusterrole.yaml" + clusterRoleBindingAssetName = "http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml" + sccRoleBindingAssetName = "http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml" +) + +var http01ProxyNetworkPolicyAssets = []string{ + "networkpolicies/http01-proxy-deny-all-networkpolicy.yaml", + "networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml", +} diff --git a/pkg/controller/http01proxy/controller.go b/pkg/controller/http01proxy/controller.go new file mode 100644 index 000000000..251740477 --- /dev/null +++ b/pkg/controller/http01proxy/controller.go @@ -0,0 +1,211 @@ +package http01proxy + +import ( + "context" + "fmt" + "reflect" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +// RequestEnqueueLabelValue is the label value used for filtering reconcile events +// to include only the resources created by the HTTP01Proxy controller. +// The label key is common.ManagedResourceLabelKey. +const RequestEnqueueLabelValue = "cert-manager-http01-proxy" + +// Reconciler reconciles an HTTP01Proxy object. +type Reconciler struct { + common.CtrlClient + + ctx context.Context + eventRecorder record.EventRecorder + log logr.Logger + scheme *runtime.Scheme + + // cachedPlatform holds discovered platform info; immutable after first fetch. + cachedPlatform *platformInfo +} + +// +kubebuilder:rbac:groups=operator.openshift.io,resources=http01proxies,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=http01proxies/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=http01proxies/finalizers,verbs=update +// +kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete + +// New returns a new Reconciler instance. +func New(mgr ctrl.Manager) (*Reconciler, error) { + c, err := common.NewClient(mgr) + if err != nil { + return nil, err + } + return &Reconciler{ + CtrlClient: c, + ctx: context.Background(), + eventRecorder: mgr.GetEventRecorderFor(ControllerName), + log: ctrl.Log.WithName(ControllerName), + scheme: mgr.GetScheme(), + }, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + mapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request { + r.log.V(4).Info("received reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + + objLabels := obj.GetLabels() + if objLabels != nil && objLabels[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue { + namespace := obj.GetNamespace() + if namespace == "" { + namespace = common.OperatorNamespace + } + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: http01proxyObjectName, + Namespace: namespace, + }, + }, + } + } + + r.log.V(4).Info("object not of interest, ignoring", "object", fmt.Sprintf("%T", obj), "name", obj.GetName()) + return []reconcile.Request{} + } + + controllerManagedResources := predicate.NewPredicateFuncs(func(object client.Object) bool { + return object.GetLabels() != nil && object.GetLabels()[common.ManagedResourceLabelKey] == RequestEnqueueLabelValue + }) + + controllerManagedResourcePredicates := builder.WithPredicates(controllerManagedResources) + withIgnoreStatusUpdatePredicates := builder.WithPredicates(predicate.GenerationChangedPredicate{}, controllerManagedResources) + + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.HTTP01Proxy{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Named(ControllerName). + Watches(&appsv1.DaemonSet{}, handler.EnqueueRequestsFromMapFunc(mapFunc), withIgnoreStatusUpdatePredicates). + Watches(&rbacv1.ClusterRole{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.ClusterRoleBinding{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&networkingv1.NetworkPolicy{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Complete(r) +} + +// Reconcile compares the state specified by the HTTP01Proxy object against the actual cluster state. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.log.V(1).Info("reconciling", "request", req) + + proxy := &v1alpha1.HTTP01Proxy{} + if err := r.Get(ctx, req.NamespacedName, proxy); err != nil { + if errors.IsNotFound(err) { + r.log.V(1).Info("http01proxy object not found, skipping reconciliation", "request", req) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("failed to fetch http01proxy %q during reconciliation: %w", req.NamespacedName, err) + } + + if !proxy.DeletionTimestamp.IsZero() { + r.log.V(1).Info("http01proxy is marked for deletion", "namespace", req.NamespacedName) + + if err := r.cleanUp(proxy); err != nil { + return ctrl.Result{}, fmt.Errorf("clean up failed for %q http01proxy deletion: %w", req.NamespacedName, err) + } + + if err := r.removeFinalizer(ctx, proxy); err != nil { + return ctrl.Result{}, err + } + + r.log.V(1).Info("removed finalizer, cleanup complete", "request", req.NamespacedName) + return ctrl.Result{}, nil + } + + if err := r.addFinalizer(ctx, proxy); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update %q http01proxy with finalizers: %w", req.NamespacedName, err) + } + + return r.processReconcileRequest(ctx, proxy, req.NamespacedName) +} + +func (r *Reconciler) processReconcileRequest(ctx context.Context, proxy *v1alpha1.HTTP01Proxy, req types.NamespacedName) (ctrl.Result, error) { + if !common.ContainsAnnotation(proxy, controllerProcessedAnnotation) && reflect.DeepEqual(proxy.Status, v1alpha1.HTTP01ProxyStatus{}) { + r.log.V(1).Info("starting reconciliation of newly created http01proxy", "namespace", proxy.GetNamespace(), "name", proxy.GetName()) + } + + if err := r.reconcileHTTP01ProxyDeployment(proxy); err != nil { + r.log.Error(err, "failed to reconcile HTTP01Proxy deployment", "request", req) + return r.handleReconcileError(ctx, proxy, err) + } + + return r.handleReconcileSuccess(ctx, proxy) +} + +func (r *Reconciler) handleReconcileError(ctx context.Context, proxy *v1alpha1.HTTP01Proxy, err error) (ctrl.Result, error) { + if common.IsIrrecoverableError(err) { + if setConditions(proxy, metav1.ConditionTrue, v1alpha1.ReasonFailed, + fmt.Sprintf("reconciliation failed with irrecoverable error: %v", err), + metav1.ConditionFalse, v1alpha1.ReasonReady, "") { + return ctrl.Result{}, r.updateCondition(ctx, proxy, nil) + } + return ctrl.Result{}, nil + } + + if setConditions(proxy, metav1.ConditionFalse, v1alpha1.ReasonReady, "", + metav1.ConditionFalse, v1alpha1.ReasonInProgress, + fmt.Sprintf("reconciliation failed, retrying: %v", err)) { + if errUpdate := r.updateCondition(ctx, proxy, err); errUpdate != nil { + return ctrl.Result{}, errUpdate + } + } + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil +} + +func (r *Reconciler) handleReconcileSuccess(ctx context.Context, proxy *v1alpha1.HTTP01Proxy) (ctrl.Result, error) { + var errUpdate error + if setConditions(proxy, metav1.ConditionFalse, v1alpha1.ReasonReady, "", + metav1.ConditionTrue, v1alpha1.ReasonReady, "reconciliation successful") { + errUpdate = r.updateCondition(ctx, proxy, nil) + } + return ctrl.Result{}, errUpdate +} + +// cleanUp handles deletion of http01proxy gracefully. +func (r *Reconciler) cleanUp(proxy *v1alpha1.HTTP01Proxy) error { + r.log.V(1).Info("cleaning up http01proxy resources", "namespace", proxy.GetNamespace(), "name", proxy.GetName()) + r.eventRecorder.Eventf(proxy, corev1.EventTypeNormal, "CleanUp", "cleaning up resources for http01proxy %s/%s", proxy.GetNamespace(), proxy.GetName()) + + if err := r.deleteDaemonSet(proxy); err != nil { + return fmt.Errorf("failed to delete daemonset: %w", err) + } + if err := r.deleteServiceAccount(proxy); err != nil { + return fmt.Errorf("failed to delete serviceaccount: %w", err) + } + if err := r.deleteRBACResources(); err != nil { + return fmt.Errorf("failed to delete rbac resources: %w", err) + } + if err := r.deleteNetworkPolicies(proxy); err != nil { + return fmt.Errorf("failed to delete network policies: %w", err) + } + + return nil +} diff --git a/pkg/controller/http01proxy/daemonsets.go b/pkg/controller/http01proxy/daemonsets.go new file mode 100644 index 000000000..d5681e24e --- /dev/null +++ b/pkg/controller/http01proxy/daemonsets.go @@ -0,0 +1,98 @@ +package http01proxy + +import ( + "fmt" + "os" + "strconv" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyDaemonSet(proxy *v1alpha1.HTTP01Proxy, resourceLabels map[string]string) error { + desired, err := r.getDaemonSetObject(proxy, resourceLabels) + if err != nil { + return common.NewIrrecoverableError(err, "failed to build daemonset object") + } + + if err := r.createOrUpdateResource(r.ctx, desired); err != nil { + return err + } + + r.updateImageInStatus(proxy, desired) + return nil +} + +func (r *Reconciler) getDaemonSetObject(proxy *v1alpha1.HTTP01Proxy, resourceLabels map[string]string) (*appsv1.DaemonSet, error) { + ds := common.DecodeObjBytes[*appsv1.DaemonSet](codecs, appsv1.SchemeGroupVersion, assets.MustAsset(daemonsetAssetName)) + + ds.SetNamespace(proxy.GetNamespace()) + common.UpdateResourceLabels(ds, resourceLabels) + ds.Spec.Template.Labels = resourceLabels + + // Update image + image := os.Getenv(http01proxyImageNameEnvVarName) + if image == "" { + return nil, fmt.Errorf("environment variable %s is not set", http01proxyImageNameEnvVarName) + } + if len(ds.Spec.Template.Spec.Containers) > 0 { + ds.Spec.Template.Spec.Containers[0].Image = image + } + + // Update port configuration + port := r.getInternalPort(proxy) + r.updateDaemonSetPort(ds, port) + + return ds, nil +} + +func (r *Reconciler) updateDaemonSetPort(ds *appsv1.DaemonSet, port int32) { + if len(ds.Spec.Template.Spec.Containers) == 0 { + return + } + + container := &ds.Spec.Template.Spec.Containers[0] + + // Update container ports + for i := range container.Ports { + if container.Ports[i].Name == "proxy" { + container.Ports[i].ContainerPort = port + container.Ports[i].HostPort = port + } + } + + // Update PROXY_PORT env var + portStr := strconv.FormatInt(int64(port), 10) + envUpdated := false + for i := range container.Env { + if container.Env[i].Name == "PROXY_PORT" { + container.Env[i].Value = portStr + envUpdated = true + break + } + } + if !envUpdated { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "PROXY_PORT", + Value: portStr, + }) + } +} + +func (r *Reconciler) updateImageInStatus(proxy *v1alpha1.HTTP01Proxy, ds *appsv1.DaemonSet) { + if len(ds.Spec.Template.Spec.Containers) > 0 { + proxy.Status.ProxyImage = ds.Spec.Template.Spec.Containers[0].Image + } +} + +func (r *Reconciler) deleteDaemonSet(proxy *v1alpha1.HTTP01Proxy) error { + return r.deleteIfExists(r.ctx, &appsv1.DaemonSet{}, client.ObjectKey{ + Namespace: proxy.GetNamespace(), + Name: http01proxyCommonName, + }) +} diff --git a/pkg/controller/http01proxy/infrastructure.go b/pkg/controller/http01proxy/infrastructure.go new file mode 100644 index 000000000..8046e0623 --- /dev/null +++ b/pkg/controller/http01proxy/infrastructure.go @@ -0,0 +1,101 @@ +package http01proxy + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" +) + +const ( + // platformBareMetal is the platform type for baremetal clusters. + platformBareMetal = "BareMetal" +) + +// platformInfo holds the discovered platform details needed to decide +// whether the HTTP01 proxy should be deployed. +type platformInfo struct { + platformType string + apiVIPs []string + ingressVIPs []string +} + +// getOrDiscoverPlatform returns cached platform info, or fetches it on first call. +func (r *Reconciler) getOrDiscoverPlatform(ctx context.Context) (*platformInfo, error) { + if r.cachedPlatform != nil { + return r.cachedPlatform, nil + } + info, err := r.discoverPlatform(ctx) + if err != nil { + return nil, err + } + r.cachedPlatform = info + return info, nil +} + +// discoverPlatform reads the Infrastructure CR and returns platform details. +func (r *Reconciler) discoverPlatform(ctx context.Context) (*platformInfo, error) { + infra := &unstructured.Unstructured{} + infra.SetGroupVersionKind(infrastructureGVK) + + if err := r.Get(ctx, types.NamespacedName{Name: "cluster"}, infra); err != nil { + return nil, fmt.Errorf("failed to get infrastructure/cluster: %w", err) + } + + platformType, found, err := unstructured.NestedString(infra.Object, "status", "platformStatus", "type") + if err != nil { + return nil, fmt.Errorf("failed to parse infrastructure status.platformStatus.type: %w", err) + } + if !found { + return nil, fmt.Errorf("infrastructure status.platformStatus.type not found") + } + + info := &platformInfo{ + platformType: platformType, + } + + // Extract VIPs based on platform type + switch platformType { + case platformBareMetal: + apiVIPs, _, err := unstructured.NestedStringSlice(infra.Object, "status", "platformStatus", "baremetal", "apiServerInternalIPs") + if err != nil { + return nil, fmt.Errorf("failed to parse baremetal.apiServerInternalIPs: %w", err) + } + ingressVIPs, _, err := unstructured.NestedStringSlice(infra.Object, "status", "platformStatus", "baremetal", "ingressIPs") + if err != nil { + return nil, fmt.Errorf("failed to parse baremetal.ingressIPs: %w", err) + } + info.apiVIPs = apiVIPs + info.ingressVIPs = ingressVIPs + } + + return info, nil +} + +// validatePlatform checks whether the platform supports HTTP01 proxy deployment. +// Returns a human-readable reason if the platform is not supported, or empty string if OK. +func validatePlatform(info *platformInfo) string { + if info.platformType != platformBareMetal { + return fmt.Sprintf("platform type %q is not supported; HTTP01 proxy is only supported on BareMetal platforms", info.platformType) + } + + if len(info.apiVIPs) == 0 { + return "no API server VIPs found in infrastructure status; cannot deploy HTTP01 proxy" + } + + if len(info.ingressVIPs) == 0 { + return "no ingress VIPs found in infrastructure status; cannot deploy HTTP01 proxy" + } + + // If any API VIP equals any ingress VIP, proxy is not needed + for _, apiVIP := range info.apiVIPs { + for _, ingressVIP := range info.ingressVIPs { + if apiVIP == ingressVIP { + return fmt.Sprintf("API VIP (%s) and ingress VIP (%s) are the same; HTTP01 proxy is not needed", apiVIP, ingressVIP) + } + } + } + + return "" +} diff --git a/pkg/controller/http01proxy/install_http01proxy.go b/pkg/controller/http01proxy/install_http01proxy.go new file mode 100644 index 000000000..508d4ecca --- /dev/null +++ b/pkg/controller/http01proxy/install_http01proxy.go @@ -0,0 +1,54 @@ +package http01proxy + +import ( + "fmt" + "maps" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +func (r *Reconciler) reconcileHTTP01ProxyDeployment(proxy *v1alpha1.HTTP01Proxy) error { + // Validate platform before deploying any resources + info, err := r.getOrDiscoverPlatform(r.ctx) + if err != nil { + return common.NewRetryRequiredError(err, "failed to discover platform") + } + + if reason := validatePlatform(info); reason != "" { + r.log.V(1).Info("platform not supported for HTTP01 proxy", "reason", reason, "platformType", info.platformType) + return common.NewIrrecoverableError(fmt.Errorf("platform validation failed"), "%s", reason) + } + + resourceLabels := make(map[string]string) + maps.Copy(resourceLabels, controllerDefaultResourceLabels) + + if err := r.createOrApplyNetworkPolicies(proxy, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile network policy resources") + return err + } + + if err := r.createOrApplyServiceAccount(proxy, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile serviceaccount resource") + return err + } + + if err := r.createOrApplyRBACResources(proxy, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile rbac resources") + return err + } + + if err := r.createOrApplyDaemonSet(proxy, resourceLabels); err != nil { + r.log.Error(err, "failed to reconcile daemonset resource") + return err + } + + if common.AddAnnotation(proxy, controllerProcessedAnnotation, "true") { + if err := r.UpdateWithRetry(r.ctx, proxy); err != nil { + return fmt.Errorf("failed to update processed annotation to %s/%s: %w", proxy.GetNamespace(), proxy.GetName(), err) + } + } + + r.log.V(4).Info("finished reconciliation of http01proxy", "namespace", proxy.GetNamespace(), "name", proxy.GetName()) + return nil +} diff --git a/pkg/controller/http01proxy/networkpolicies.go b/pkg/controller/http01proxy/networkpolicies.go new file mode 100644 index 000000000..9efc375bf --- /dev/null +++ b/pkg/controller/http01proxy/networkpolicies.go @@ -0,0 +1,36 @@ +package http01proxy + +import ( + "fmt" + + networkingv1 "k8s.io/api/networking/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyNetworkPolicies(proxy *v1alpha1.HTTP01Proxy, resourceLabels map[string]string) error { + for _, assetName := range http01ProxyNetworkPolicyAssets { + np := common.DecodeObjBytes[*networkingv1.NetworkPolicy](codecs, networkingv1.SchemeGroupVersion, assets.MustAsset(assetName)) + np.SetNamespace(proxy.GetNamespace()) + common.UpdateResourceLabels(np, resourceLabels) + + if err := r.createOrUpdateResource(r.ctx, np); err != nil { + return fmt.Errorf("failed to reconcile network policy %s: %w", assetName, err) + } + } + return nil +} + +func (r *Reconciler) deleteNetworkPolicies(proxy *v1alpha1.HTTP01Proxy) error { + for _, assetName := range http01ProxyNetworkPolicyAssets { + np := common.DecodeObjBytes[*networkingv1.NetworkPolicy](codecs, networkingv1.SchemeGroupVersion, assets.MustAsset(assetName)) + key := client.ObjectKey{Namespace: proxy.GetNamespace(), Name: np.GetName()} + if err := r.deleteIfExists(r.ctx, &networkingv1.NetworkPolicy{}, key); err != nil { + return fmt.Errorf("failed to delete network policy %q: %w", key.Name, err) + } + } + return nil +} diff --git a/pkg/controller/http01proxy/rbacs.go b/pkg/controller/http01proxy/rbacs.go new file mode 100644 index 000000000..d36156073 --- /dev/null +++ b/pkg/controller/http01proxy/rbacs.go @@ -0,0 +1,53 @@ +package http01proxy + +import ( + "fmt" + + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyRBACResources(proxy *v1alpha1.HTTP01Proxy, resourceLabels map[string]string) error { + cr := common.DecodeObjBytes[*rbacv1.ClusterRole](codecs, rbacv1.SchemeGroupVersion, assets.MustAsset(clusterRoleAssetName)) + common.UpdateResourceLabels(cr, resourceLabels) + if err := r.createOrUpdateResource(r.ctx, cr); err != nil { + return fmt.Errorf("failed to reconcile clusterrole: %w", err) + } + + crb := common.DecodeObjBytes[*rbacv1.ClusterRoleBinding](codecs, rbacv1.SchemeGroupVersion, assets.MustAsset(clusterRoleBindingAssetName)) + common.UpdateResourceLabels(crb, resourceLabels) + for i := range crb.Subjects { + if crb.Subjects[i].Kind == "ServiceAccount" { + crb.Subjects[i].Namespace = proxy.GetNamespace() + } + } + if err := r.createOrUpdateResource(r.ctx, crb); err != nil { + return fmt.Errorf("failed to reconcile clusterrolebinding: %w", err) + } + + sccCRB := common.DecodeObjBytes[*rbacv1.ClusterRoleBinding](codecs, rbacv1.SchemeGroupVersion, assets.MustAsset(sccRoleBindingAssetName)) + common.UpdateResourceLabels(sccCRB, resourceLabels) + for i := range sccCRB.Subjects { + if sccCRB.Subjects[i].Kind == "ServiceAccount" { + sccCRB.Subjects[i].Namespace = proxy.GetNamespace() + } + } + if err := r.createOrUpdateResource(r.ctx, sccCRB); err != nil { + return fmt.Errorf("failed to reconcile scc clusterrolebinding: %w", err) + } + + return nil +} + +func (r *Reconciler) deleteRBACResources() error { + for _, name := range []string{http01proxyCommonName, http01proxyCommonName + "-scc"} { + if err := r.deleteIfExists(r.ctx, &rbacv1.ClusterRoleBinding{}, client.ObjectKey{Name: name}); err != nil { + return fmt.Errorf("failed to delete clusterrolebinding %q: %w", name, err) + } + } + return r.deleteIfExists(r.ctx, &rbacv1.ClusterRole{}, client.ObjectKey{Name: http01proxyCommonName}) +} diff --git a/pkg/controller/http01proxy/serviceaccounts.go b/pkg/controller/http01proxy/serviceaccounts.go new file mode 100644 index 000000000..365fa082e --- /dev/null +++ b/pkg/controller/http01proxy/serviceaccounts.go @@ -0,0 +1,24 @@ +package http01proxy + +import ( + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServiceAccount(proxy *v1alpha1.HTTP01Proxy, resourceLabels map[string]string) error { + sa := common.DecodeObjBytes[*corev1.ServiceAccount](codecs, corev1.SchemeGroupVersion, assets.MustAsset(serviceAccountAssetName)) + sa.SetNamespace(proxy.GetNamespace()) + common.UpdateResourceLabels(sa, resourceLabels) + return r.createOrUpdateResource(r.ctx, sa) +} + +func (r *Reconciler) deleteServiceAccount(proxy *v1alpha1.HTTP01Proxy) error { + return r.deleteIfExists(r.ctx, &corev1.ServiceAccount{}, client.ObjectKey{ + Namespace: proxy.GetNamespace(), + Name: http01proxyCommonName, + }) +} diff --git a/pkg/controller/http01proxy/utils.go b/pkg/controller/http01proxy/utils.go new file mode 100644 index 000000000..f9af2b400 --- /dev/null +++ b/pkg/controller/http01proxy/utils.go @@ -0,0 +1,203 @@ +package http01proxy + +import ( + "context" + "fmt" + "reflect" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/controller/common" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +func init() { + if err := appsv1.AddToScheme(scheme); err != nil { + panic(err) + } + if err := corev1.AddToScheme(scheme); err != nil { + panic(err) + } + if err := networkingv1.AddToScheme(scheme); err != nil { + panic(err) + } + if err := rbacv1.AddToScheme(scheme); err != nil { + panic(err) + } +} + +func (r *Reconciler) updateStatus(ctx context.Context, changed *v1alpha1.HTTP01Proxy) error { + namespacedName := client.ObjectKeyFromObject(changed) + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + r.log.V(4).Info("updating http01proxy status", "request", namespacedName) + current := &v1alpha1.HTTP01Proxy{} + if err := r.Get(ctx, namespacedName, current); err != nil { + return fmt.Errorf("failed to fetch http01proxy %q for status update: %w", namespacedName, err) + } + changed.Status.DeepCopyInto(¤t.Status) + if err := r.StatusUpdate(ctx, current); err != nil { + return fmt.Errorf("failed to update http01proxy %q status: %w", namespacedName, err) + } + return nil + }) +} + +func (r *Reconciler) addFinalizer(ctx context.Context, proxy *v1alpha1.HTTP01Proxy) error { + namespacedName := client.ObjectKeyFromObject(proxy) + if !controllerutil.ContainsFinalizer(proxy, finalizer) { + if !controllerutil.AddFinalizer(proxy, finalizer) { + return fmt.Errorf("failed to create %q http01proxy object with finalizers added", namespacedName) + } + if err := r.UpdateWithRetry(ctx, proxy); err != nil { + return fmt.Errorf("failed to add finalizers on %q http01proxy with %w", namespacedName, err) + } + updated := &v1alpha1.HTTP01Proxy{} + if err := r.Get(ctx, namespacedName, updated); err != nil { + return fmt.Errorf("failed to fetch http01proxy %q after updating finalizers: %w", namespacedName, err) + } + updated.DeepCopyInto(proxy) + } + return nil +} + +func (r *Reconciler) removeFinalizer(ctx context.Context, proxy *v1alpha1.HTTP01Proxy) error { + namespacedName := client.ObjectKeyFromObject(proxy) + if controllerutil.ContainsFinalizer(proxy, finalizer) { + if !controllerutil.RemoveFinalizer(proxy, finalizer) { + return fmt.Errorf("failed to create %q http01proxy object with finalizers removed", namespacedName) + } + if err := r.UpdateWithRetry(ctx, proxy); err != nil { + return fmt.Errorf("failed to remove finalizers on %q http01proxy with %w", namespacedName, err) + } + } + return nil +} + +func hasObjectChanged(desired, fetched client.Object) bool { + if reflect.TypeOf(desired) != reflect.TypeOf(fetched) { + panic("both objects to be compared must be of same type") + } + + var objectModified bool + switch d := desired.(type) { + case *rbacv1.ClusterRole: + f, _ := fetched.(*rbacv1.ClusterRole) + objectModified = !reflect.DeepEqual(d.Rules, f.Rules) + case *rbacv1.ClusterRoleBinding: + f, _ := fetched.(*rbacv1.ClusterRoleBinding) + objectModified = !reflect.DeepEqual(d.RoleRef, f.RoleRef) || !reflect.DeepEqual(d.Subjects, f.Subjects) + case *appsv1.DaemonSet: + f, _ := fetched.(*appsv1.DaemonSet) + objectModified = daemonSetSpecModified(d, f) + case *networkingv1.NetworkPolicy: + f, _ := fetched.(*networkingv1.NetworkPolicy) + objectModified = !reflect.DeepEqual(d.Spec, f.Spec) + case *corev1.ServiceAccount: + return common.ObjectMetadataModified(desired, fetched) + default: + panic(fmt.Sprintf("unsupported object type: %T", desired)) + } + return objectModified || common.ObjectMetadataModified(desired, fetched) +} + +func daemonSetSpecModified(desired, fetched *appsv1.DaemonSet) bool { + if !reflect.DeepEqual(desired.Spec.Selector.MatchLabels, fetched.Spec.Selector.MatchLabels) { + return true + } + if !reflect.DeepEqual(desired.Spec.Template.Labels, fetched.Spec.Template.Labels) || + len(desired.Spec.Template.Spec.Containers) != len(fetched.Spec.Template.Spec.Containers) { + return true + } + if len(desired.Spec.Template.Spec.Containers) > 0 && len(fetched.Spec.Template.Spec.Containers) > 0 { + desiredContainer := desired.Spec.Template.Spec.Containers[0] + fetchedContainer := fetched.Spec.Template.Spec.Containers[0] + if desiredContainer.Image != fetchedContainer.Image || + desiredContainer.Name != fetchedContainer.Name || + !reflect.DeepEqual(desiredContainer.Env, fetchedContainer.Env) || + !reflect.DeepEqual(desiredContainer.Ports, fetchedContainer.Ports) { + return true + } + } + if desired.Spec.Template.Spec.ServiceAccountName != fetched.Spec.Template.Spec.ServiceAccountName || + !reflect.DeepEqual(desired.Spec.Template.Spec.NodeSelector, fetched.Spec.Template.Spec.NodeSelector) { + return true + } + return false +} + +func (r *Reconciler) updateCondition(ctx context.Context, proxy *v1alpha1.HTTP01Proxy, prependErr error) error { + if err := r.updateStatus(ctx, proxy); err != nil { + errUpdate := fmt.Errorf("failed to update %s/%s status: %w", proxy.GetNamespace(), proxy.GetName(), err) + if prependErr != nil { + return utilerrors.NewAggregate([]error{prependErr, errUpdate}) + } + return errUpdate + } + return prependErr +} + +func (r *Reconciler) getInternalPort(proxy *v1alpha1.HTTP01Proxy) int32 { + if proxy.Spec.Mode == v1alpha1.HTTP01ProxyModeCustom && + proxy.Spec.CustomDeployment != nil && + proxy.Spec.CustomDeployment.InternalPort > 0 { + return proxy.Spec.CustomDeployment.InternalPort + } + return defaultInternalPort +} + +func (r *Reconciler) createOrUpdateResource(ctx context.Context, desired client.Object) error { + key := client.ObjectKeyFromObject(desired) + kind := desired.GetObjectKind().GroupVersionKind().Kind + + r.log.V(2).Info("creating resource", "kind", kind, "name", key) + if err := r.Create(ctx, desired); err != nil { + if !errors.IsAlreadyExists(err) { + return common.FromClientError(err, "failed to create %s %q", kind, key) + } + fetched, _ := desired.DeepCopyObject().(client.Object) + if err := r.Get(ctx, key, fetched); err != nil { + return common.FromClientError(err, "failed to get %s %q for update", kind, key) + } + if hasObjectChanged(desired, fetched) { + r.log.V(2).Info("updating resource", "kind", kind, "name", key) + desired.SetResourceVersion(fetched.GetResourceVersion()) + if err := r.Update(ctx, desired); err != nil { + return common.FromClientError(err, "failed to update %s %q", kind, key) + } + } + } + + return nil +} + +func setConditions(proxy *v1alpha1.HTTP01Proxy, degradedStatus metav1.ConditionStatus, degradedReason, degradedMsg string, readyStatus metav1.ConditionStatus, readyReason, readyMsg string) bool { + degradedChanged := proxy.Status.SetCondition(v1alpha1.Degraded, degradedStatus, degradedReason, degradedMsg) + readyChanged := proxy.Status.SetCondition(v1alpha1.Ready, readyStatus, readyReason, readyMsg) + return degradedChanged || readyChanged +} + +// deleteIfExists deletes a resource, ignoring NotFound errors. +func (r *Reconciler) deleteIfExists(ctx context.Context, obj client.Object, key client.ObjectKey) error { + obj.SetName(key.Name) + obj.SetNamespace(key.Namespace) + if err := r.Delete(ctx, obj); client.IgnoreNotFound(err) != nil { + return fmt.Errorf("failed to delete %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, key, err) + } + return nil +} diff --git a/pkg/features/features_test.go b/pkg/features/features_test.go index 7530ebd64..c038ce04c 100644 --- a/pkg/features/features_test.go +++ b/pkg/features/features_test.go @@ -65,6 +65,7 @@ var expectedDefaultFeatureState = map[bool][]featuregate.Feature{ // list of features which are expected to be disabled at runtime. false: { featuregate.Feature("TrustManager"), + featuregate.Feature("HTTP01Proxy"), }, } @@ -87,7 +88,7 @@ func TestFeatureGates(t *testing.T) { } slices.Sort(knownOperatorFeatures) - assert.Equal(t, knownOperatorFeatures, testFeatureNames, + assert.ElementsMatch(t, knownOperatorFeatures, testFeatureNames, `the list of features known to the operator differ from what is being tested here, it could be that there was a new Feature added to the api which wasn't added to the tests. Please verify "api/operator/v1alpha1" and "pkg/features" have identical features.`) @@ -102,7 +103,7 @@ func TestFeatureGates(t *testing.T) { } }) - t.Run("all TechPreview features should be disabled by default", func(t *testing.T) { + t.Run("all pre-GA features should be disabled by default", func(t *testing.T) { feats := mutableFeatureGate.GetAll() for feat, spec := range feats { // skip "AllBeta", "AllAlpha": our operator does not use those @@ -110,9 +111,10 @@ func TestFeatureGates(t *testing.T) { continue } - assert.Equal(t, spec.PreRelease == "TechPreview", !spec.Default, - "prerelease TechPreview %q feature should default to disabled", - feat) + isPreGA := spec.PreRelease == "TechPreview" || spec.PreRelease == featuregate.Alpha || spec.PreRelease == featuregate.Beta + assert.Equal(t, isPreGA, !spec.Default, + "pre-GA %q feature (prerelease=%s) should default to disabled", + feat, spec.PreRelease) } }) diff --git a/pkg/operator/applyconfigurations/internal/internal.go b/pkg/operator/applyconfigurations/internal/internal.go index 6b910267e..671b8b064 100644 --- a/pkg/operator/applyconfigurations/internal/internal.go +++ b/pkg/operator/applyconfigurations/internal/internal.go @@ -33,6 +33,16 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: __untyped_deduced_ elementRelationship: separable +- name: com.github.openshift.cert-manager-operator.api.operator.v1alpha1.HTTP01Proxy + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: com.github.openshift.cert-manager-operator.api.operator.v1alpha1.IstioCSR scalar: untyped list: diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxy.go b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxy.go new file mode 100644 index 000000000..0a3beb5d9 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxy.go @@ -0,0 +1,265 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + internal "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// HTTP01ProxyApplyConfiguration represents a declarative configuration of the HTTP01Proxy type for use +// with apply. +type HTTP01ProxyApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *HTTP01ProxySpecApplyConfiguration `json:"spec,omitempty"` + Status *HTTP01ProxyStatusApplyConfiguration `json:"status,omitempty"` +} + +// HTTP01Proxy constructs a declarative configuration of the HTTP01Proxy type for use with +// apply. +func HTTP01Proxy(name, namespace string) *HTTP01ProxyApplyConfiguration { + b := &HTTP01ProxyApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("HTTP01Proxy") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b +} + +// ExtractHTTP01Proxy extracts the applied configuration owned by fieldManager from +// hTTP01Proxy. If no managedFields are found in hTTP01Proxy for fieldManager, a +// HTTP01ProxyApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// hTTP01Proxy must be a unmodified HTTP01Proxy API object that was retrieved from the Kubernetes API. +// ExtractHTTP01Proxy provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractHTTP01Proxy(hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, fieldManager string) (*HTTP01ProxyApplyConfiguration, error) { + return extractHTTP01Proxy(hTTP01Proxy, fieldManager, "") +} + +// ExtractHTTP01ProxyStatus is the same as ExtractHTTP01Proxy except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractHTTP01ProxyStatus(hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, fieldManager string) (*HTTP01ProxyApplyConfiguration, error) { + return extractHTTP01Proxy(hTTP01Proxy, fieldManager, "status") +} + +func extractHTTP01Proxy(hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, fieldManager string, subresource string) (*HTTP01ProxyApplyConfiguration, error) { + b := &HTTP01ProxyApplyConfiguration{} + err := managedfields.ExtractInto(hTTP01Proxy, internal.Parser().Type("com.github.openshift.cert-manager-operator.api.operator.v1alpha1.HTTP01Proxy"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(hTTP01Proxy.Name) + b.WithNamespace(hTTP01Proxy.Namespace) + + b.WithKind("HTTP01Proxy") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b, nil +} +func (b HTTP01ProxyApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithKind(value string) *HTTP01ProxyApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithAPIVersion(value string) *HTTP01ProxyApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithName(value string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithGenerateName(value string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithNamespace(value string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithUID(value types.UID) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithResourceVersion(value string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithGeneration(value int64) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithCreationTimestamp(value metav1.Time) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *HTTP01ProxyApplyConfiguration) WithLabels(entries map[string]string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *HTTP01ProxyApplyConfiguration) WithAnnotations(entries map[string]string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *HTTP01ProxyApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *HTTP01ProxyApplyConfiguration) WithFinalizers(values ...string) *HTTP01ProxyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *HTTP01ProxyApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithSpec(value *HTTP01ProxySpecApplyConfiguration) *HTTP01ProxyApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *HTTP01ProxyApplyConfiguration) WithStatus(value *HTTP01ProxyStatusApplyConfiguration) *HTTP01ProxyApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *HTTP01ProxyApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *HTTP01ProxyApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *HTTP01ProxyApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *HTTP01ProxyApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxycustomdeploymentspec.go b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxycustomdeploymentspec.go new file mode 100644 index 000000000..3fc418cc2 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxycustomdeploymentspec.go @@ -0,0 +1,23 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// HTTP01ProxyCustomDeploymentSpecApplyConfiguration represents a declarative configuration of the HTTP01ProxyCustomDeploymentSpec type for use +// with apply. +type HTTP01ProxyCustomDeploymentSpecApplyConfiguration struct { + InternalPort *int32 `json:"internalPort,omitempty"` +} + +// HTTP01ProxyCustomDeploymentSpecApplyConfiguration constructs a declarative configuration of the HTTP01ProxyCustomDeploymentSpec type for use with +// apply. +func HTTP01ProxyCustomDeploymentSpec() *HTTP01ProxyCustomDeploymentSpecApplyConfiguration { + return &HTTP01ProxyCustomDeploymentSpecApplyConfiguration{} +} + +// WithInternalPort sets the InternalPort field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the InternalPort field is set to the value of the last call. +func (b *HTTP01ProxyCustomDeploymentSpecApplyConfiguration) WithInternalPort(value int32) *HTTP01ProxyCustomDeploymentSpecApplyConfiguration { + b.InternalPort = &value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxyspec.go b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxyspec.go new file mode 100644 index 000000000..eac3fd562 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxyspec.go @@ -0,0 +1,36 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// HTTP01ProxySpecApplyConfiguration represents a declarative configuration of the HTTP01ProxySpec type for use +// with apply. +type HTTP01ProxySpecApplyConfiguration struct { + Mode *operatorv1alpha1.HTTP01ProxyMode `json:"mode,omitempty"` + CustomDeployment *HTTP01ProxyCustomDeploymentSpecApplyConfiguration `json:"customDeployment,omitempty"` +} + +// HTTP01ProxySpecApplyConfiguration constructs a declarative configuration of the HTTP01ProxySpec type for use with +// apply. +func HTTP01ProxySpec() *HTTP01ProxySpecApplyConfiguration { + return &HTTP01ProxySpecApplyConfiguration{} +} + +// WithMode sets the Mode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Mode field is set to the value of the last call. +func (b *HTTP01ProxySpecApplyConfiguration) WithMode(value operatorv1alpha1.HTTP01ProxyMode) *HTTP01ProxySpecApplyConfiguration { + b.Mode = &value + return b +} + +// WithCustomDeployment sets the CustomDeployment field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CustomDeployment field is set to the value of the last call. +func (b *HTTP01ProxySpecApplyConfiguration) WithCustomDeployment(value *HTTP01ProxyCustomDeploymentSpecApplyConfiguration) *HTTP01ProxySpecApplyConfiguration { + b.CustomDeployment = value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxystatus.go b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxystatus.go new file mode 100644 index 000000000..9ac6ff9ed --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/http01proxystatus.go @@ -0,0 +1,41 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// HTTP01ProxyStatusApplyConfiguration represents a declarative configuration of the HTTP01ProxyStatus type for use +// with apply. +type HTTP01ProxyStatusApplyConfiguration struct { + ConditionalStatusApplyConfiguration `json:",omitempty,inline"` + ProxyImage *string `json:"proxyImage,omitempty"` +} + +// HTTP01ProxyStatusApplyConfiguration constructs a declarative configuration of the HTTP01ProxyStatus type for use with +// apply. +func HTTP01ProxyStatus() *HTTP01ProxyStatusApplyConfiguration { + return &HTTP01ProxyStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *HTTP01ProxyStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *HTTP01ProxyStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.ConditionalStatusApplyConfiguration.Conditions = append(b.ConditionalStatusApplyConfiguration.Conditions, *values[i]) + } + return b +} + +// WithProxyImage sets the ProxyImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProxyImage field is set to the value of the last call. +func (b *HTTP01ProxyStatusApplyConfiguration) WithProxyImage(value string) *HTTP01ProxyStatusApplyConfiguration { + b.ProxyImage = &value + return b +} diff --git a/pkg/operator/applyconfigurations/utils.go b/pkg/operator/applyconfigurations/utils.go index 37cba6978..ba97e423e 100644 --- a/pkg/operator/applyconfigurations/utils.go +++ b/pkg/operator/applyconfigurations/utils.go @@ -38,6 +38,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.DefaultCAPackageConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DeploymentConfig"): return &operatorv1alpha1.DeploymentConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HTTP01Proxy"): + return &operatorv1alpha1.HTTP01ProxyApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HTTP01ProxyCustomDeploymentSpec"): + return &operatorv1alpha1.HTTP01ProxyCustomDeploymentSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HTTP01ProxySpec"): + return &operatorv1alpha1.HTTP01ProxySpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HTTP01ProxyStatus"): + return &operatorv1alpha1.HTTP01ProxyStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("IstioConfig"): return &operatorv1alpha1.IstioConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("IstioCSR"): diff --git a/pkg/operator/assets/bindata.go b/pkg/operator/assets/bindata.go index 98bbad883..1231267ce 100644 --- a/pkg/operator/assets/bindata.go +++ b/pkg/operator/assets/bindata.go @@ -43,6 +43,11 @@ // bindata/cert-manager-deployment/webhook/cert-manager-webhook-subjectaccessreviews-crb.yaml // bindata/cert-manager-deployment/webhook/cert-manager-webhook-svc.yaml // bindata/cert-manager-deployment/webhook/cert-manager-webhook-validatingwebhookconfiguration.yaml +// bindata/http01-proxy/cert-manager-http01-proxy-clusterrole.yaml +// bindata/http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml +// bindata/http01-proxy/cert-manager-http01-proxy-daemonset.yaml +// bindata/http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml +// bindata/http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml // bindata/istio-csr/cert-manager-istio-csr-clusterrole.yaml // bindata/istio-csr/cert-manager-istio-csr-clusterrolebinding.yaml // bindata/istio-csr/cert-manager-istio-csr-deployment.yaml @@ -59,6 +64,8 @@ // bindata/networkpolicies/cert-manager-allow-ingress-to-metrics-networkpolicy.yaml // bindata/networkpolicies/cert-manager-allow-ingress-to-webhook-networkpolicy.yaml // bindata/networkpolicies/cert-manager-deny-all-networkpolicy.yaml +// bindata/networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml +// bindata/networkpolicies/http01-proxy-deny-all-networkpolicy.yaml // bindata/networkpolicies/istio-csr-allow-egress-to-api-server-networkpolicy.yaml // bindata/networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml // bindata/networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml @@ -2281,6 +2288,226 @@ func certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfiguratio return a, nil } +var _http01ProxyCertManagerHttp01ProxyClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-http01-proxy + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - config.openshift.io + resources: + - clusterversions + - infrastructures + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - operator.openshift.io + resources: + - machineconfigurations + verbs: + - update + - apiGroups: + - machineconfiguration.openshift.io + resources: + - machineconfigs + verbs: + - get + - list + - create + - update +`) + +func http01ProxyCertManagerHttp01ProxyClusterroleYamlBytes() ([]byte, error) { + return _http01ProxyCertManagerHttp01ProxyClusterroleYaml, nil +} + +func http01ProxyCertManagerHttp01ProxyClusterroleYaml() (*asset, error) { + bytes, err := http01ProxyCertManagerHttp01ProxyClusterroleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "http01-proxy/cert-manager-http01-proxy-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _http01ProxyCertManagerHttp01ProxyClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-http01-proxy + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-http01-proxy +subjects: + - kind: ServiceAccount + name: cert-manager-http01-proxy + namespace: cert-manager-operator +`) + +func http01ProxyCertManagerHttp01ProxyClusterrolebindingYamlBytes() ([]byte, error) { + return _http01ProxyCertManagerHttp01ProxyClusterrolebindingYaml, nil +} + +func http01ProxyCertManagerHttp01ProxyClusterrolebindingYaml() (*asset, error) { + bytes, err := http01ProxyCertManagerHttp01ProxyClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _http01ProxyCertManagerHttp01ProxyDaemonsetYaml = []byte(`apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: cert-manager-http01-proxy + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + selector: + matchLabels: + app: cert-manager-http01-proxy + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator + spec: + serviceAccountName: cert-manager-http01-proxy + hostNetwork: true + nodeSelector: + node-role.kubernetes.io/master: "" + tolerations: + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + containers: + - name: http01-proxy + image: ${RELATED_IMAGE_CERT_MANAGER_HTTP01PROXY} + ports: + - name: proxy + containerPort: 8888 + hostPort: 8888 + protocol: TCP + env: + - name: PROXY_PORT + value: "8888" + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + drop: + - ALL + runAsNonRoot: false + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + priorityClassName: system-cluster-critical +`) + +func http01ProxyCertManagerHttp01ProxyDaemonsetYamlBytes() ([]byte, error) { + return _http01ProxyCertManagerHttp01ProxyDaemonsetYaml, nil +} + +func http01ProxyCertManagerHttp01ProxyDaemonsetYaml() (*asset, error) { + bytes, err := http01ProxyCertManagerHttp01ProxyDaemonsetYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "http01-proxy/cert-manager-http01-proxy-daemonset.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _http01ProxyCertManagerHttp01ProxySccRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-http01-proxy-scc + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: cert-manager-http01-proxy + namespace: cert-manager-operator +`) + +func http01ProxyCertManagerHttp01ProxySccRolebindingYamlBytes() ([]byte, error) { + return _http01ProxyCertManagerHttp01ProxySccRolebindingYaml, nil +} + +func http01ProxyCertManagerHttp01ProxySccRolebindingYaml() (*asset, error) { + bytes, err := http01ProxyCertManagerHttp01ProxySccRolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _http01ProxyCertManagerHttp01ProxyServiceaccountYaml = []byte(`apiVersion: v1 +kind: ServiceAccount +metadata: + name: cert-manager-http01-proxy + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +`) + +func http01ProxyCertManagerHttp01ProxyServiceaccountYamlBytes() ([]byte, error) { + return _http01ProxyCertManagerHttp01ProxyServiceaccountYaml, nil +} + +func http01ProxyCertManagerHttp01ProxyServiceaccountYaml() (*asset, error) { + bytes, err := http01ProxyCertManagerHttp01ProxyServiceaccountYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _istioCsrCertManagerIstioCsrClusterroleYaml = []byte(`kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -2952,6 +3179,79 @@ func networkpoliciesCertManagerDenyAllNetworkpolicyYaml() (*asset, error) { return a, nil } +var _networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYaml = []byte(`apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cert-manager-http01-proxy-allow-egress + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + podSelector: + matchLabels: + app: cert-manager-http01-proxy + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + - port: 80 + protocol: TCP +`) + +func networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYamlBytes() ([]byte, error) { + return _networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYaml, nil +} + +func networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYaml() (*asset, error) { + bytes, err := networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _networkpoliciesHttp01ProxyDenyAllNetworkpolicyYaml = []byte(`apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: cert-manager-http01-proxy-deny-all + namespace: cert-manager-operator + labels: + app: cert-manager-http01-proxy + app.kubernetes.io/name: cert-manager-http01-proxy + app.kubernetes.io/part-of: cert-manager-operator +spec: + podSelector: + matchLabels: + app: cert-manager-http01-proxy + policyTypes: + - Ingress + - Egress +`) + +func networkpoliciesHttp01ProxyDenyAllNetworkpolicyYamlBytes() ([]byte, error) { + return _networkpoliciesHttp01ProxyDenyAllNetworkpolicyYaml, nil +} + +func networkpoliciesHttp01ProxyDenyAllNetworkpolicyYaml() (*asset, error) { + bytes, err := networkpoliciesHttp01ProxyDenyAllNetworkpolicyYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "networkpolicies/http01-proxy-deny-all-networkpolicy.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _networkpoliciesIstioCsrAllowEgressToApiServerNetworkpolicyYaml = []byte(`apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -3758,6 +4058,11 @@ var _bindata = map[string]func() (*asset, error){ "cert-manager-deployment/webhook/cert-manager-webhook-subjectaccessreviews-crb.yaml": certManagerDeploymentWebhookCertManagerWebhookSubjectaccessreviewsCrbYaml, "cert-manager-deployment/webhook/cert-manager-webhook-svc.yaml": certManagerDeploymentWebhookCertManagerWebhookSvcYaml, "cert-manager-deployment/webhook/cert-manager-webhook-validatingwebhookconfiguration.yaml": certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfigurationYaml, + "http01-proxy/cert-manager-http01-proxy-clusterrole.yaml": http01ProxyCertManagerHttp01ProxyClusterroleYaml, + "http01-proxy/cert-manager-http01-proxy-clusterrolebinding.yaml": http01ProxyCertManagerHttp01ProxyClusterrolebindingYaml, + "http01-proxy/cert-manager-http01-proxy-daemonset.yaml": http01ProxyCertManagerHttp01ProxyDaemonsetYaml, + "http01-proxy/cert-manager-http01-proxy-scc-rolebinding.yaml": http01ProxyCertManagerHttp01ProxySccRolebindingYaml, + "http01-proxy/cert-manager-http01-proxy-serviceaccount.yaml": http01ProxyCertManagerHttp01ProxyServiceaccountYaml, "istio-csr/cert-manager-istio-csr-clusterrole.yaml": istioCsrCertManagerIstioCsrClusterroleYaml, "istio-csr/cert-manager-istio-csr-clusterrolebinding.yaml": istioCsrCertManagerIstioCsrClusterrolebindingYaml, "istio-csr/cert-manager-istio-csr-deployment.yaml": istioCsrCertManagerIstioCsrDeploymentYaml, @@ -3774,6 +4079,8 @@ var _bindata = map[string]func() (*asset, error){ "networkpolicies/cert-manager-allow-ingress-to-metrics-networkpolicy.yaml": networkpoliciesCertManagerAllowIngressToMetricsNetworkpolicyYaml, "networkpolicies/cert-manager-allow-ingress-to-webhook-networkpolicy.yaml": networkpoliciesCertManagerAllowIngressToWebhookNetworkpolicyYaml, "networkpolicies/cert-manager-deny-all-networkpolicy.yaml": networkpoliciesCertManagerDenyAllNetworkpolicyYaml, + "networkpolicies/http01-proxy-allow-egress-networkpolicy.yaml": networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYaml, + "networkpolicies/http01-proxy-deny-all-networkpolicy.yaml": networkpoliciesHttp01ProxyDenyAllNetworkpolicyYaml, "networkpolicies/istio-csr-allow-egress-to-api-server-networkpolicy.yaml": networkpoliciesIstioCsrAllowEgressToApiServerNetworkpolicyYaml, "networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToGrpcNetworkpolicyYaml, "networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, @@ -3889,6 +4196,13 @@ var _bintree = &bintree{nil, map[string]*bintree{ "cert-manager-webhook-validatingwebhookconfiguration.yaml": {certManagerDeploymentWebhookCertManagerWebhookValidatingwebhookconfigurationYaml, map[string]*bintree{}}, }}, }}, + "http01-proxy": {nil, map[string]*bintree{ + "cert-manager-http01-proxy-clusterrole.yaml": {http01ProxyCertManagerHttp01ProxyClusterroleYaml, map[string]*bintree{}}, + "cert-manager-http01-proxy-clusterrolebinding.yaml": {http01ProxyCertManagerHttp01ProxyClusterrolebindingYaml, map[string]*bintree{}}, + "cert-manager-http01-proxy-daemonset.yaml": {http01ProxyCertManagerHttp01ProxyDaemonsetYaml, map[string]*bintree{}}, + "cert-manager-http01-proxy-scc-rolebinding.yaml": {http01ProxyCertManagerHttp01ProxySccRolebindingYaml, map[string]*bintree{}}, + "cert-manager-http01-proxy-serviceaccount.yaml": {http01ProxyCertManagerHttp01ProxyServiceaccountYaml, map[string]*bintree{}}, + }}, "istio-csr": {nil, map[string]*bintree{ "cert-manager-istio-csr-clusterrole.yaml": {istioCsrCertManagerIstioCsrClusterroleYaml, map[string]*bintree{}}, "cert-manager-istio-csr-clusterrolebinding.yaml": {istioCsrCertManagerIstioCsrClusterrolebindingYaml, map[string]*bintree{}}, @@ -3908,6 +4222,8 @@ var _bintree = &bintree{nil, map[string]*bintree{ "cert-manager-allow-ingress-to-metrics-networkpolicy.yaml": {networkpoliciesCertManagerAllowIngressToMetricsNetworkpolicyYaml, map[string]*bintree{}}, "cert-manager-allow-ingress-to-webhook-networkpolicy.yaml": {networkpoliciesCertManagerAllowIngressToWebhookNetworkpolicyYaml, map[string]*bintree{}}, "cert-manager-deny-all-networkpolicy.yaml": {networkpoliciesCertManagerDenyAllNetworkpolicyYaml, map[string]*bintree{}}, + "http01-proxy-allow-egress-networkpolicy.yaml": {networkpoliciesHttp01ProxyAllowEgressNetworkpolicyYaml, map[string]*bintree{}}, + "http01-proxy-deny-all-networkpolicy.yaml": {networkpoliciesHttp01ProxyDenyAllNetworkpolicyYaml, map[string]*bintree{}}, "istio-csr-allow-egress-to-api-server-networkpolicy.yaml": {networkpoliciesIstioCsrAllowEgressToApiServerNetworkpolicyYaml, map[string]*bintree{}}, "istio-csr-allow-ingress-to-grpc-networkpolicy.yaml": {networkpoliciesIstioCsrAllowIngressToGrpcNetworkpolicyYaml, map[string]*bintree{}}, "istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": {networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, map[string]*bintree{}}, diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_http01proxy.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_http01proxy.go new file mode 100644 index 000000000..0fb97493a --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_http01proxy.go @@ -0,0 +1,37 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + typedoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/typed/operator/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeHTTP01Proxies implements HTTP01ProxyInterface +type fakeHTTP01Proxies struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.HTTP01Proxy, *v1alpha1.HTTP01ProxyList, *operatorv1alpha1.HTTP01ProxyApplyConfiguration] + Fake *FakeOperatorV1alpha1 +} + +func newFakeHTTP01Proxies(fake *FakeOperatorV1alpha1, namespace string) typedoperatorv1alpha1.HTTP01ProxyInterface { + return &fakeHTTP01Proxies{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.HTTP01Proxy, *v1alpha1.HTTP01ProxyList, *operatorv1alpha1.HTTP01ProxyApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("http01proxies"), + v1alpha1.SchemeGroupVersion.WithKind("HTTP01Proxy"), + func() *v1alpha1.HTTP01Proxy { return &v1alpha1.HTTP01Proxy{} }, + func() *v1alpha1.HTTP01ProxyList { return &v1alpha1.HTTP01ProxyList{} }, + func(dst, src *v1alpha1.HTTP01ProxyList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.HTTP01ProxyList) []*v1alpha1.HTTP01Proxy { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.HTTP01ProxyList, items []*v1alpha1.HTTP01Proxy) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go index aaca26cb7..ddd167035 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go @@ -16,6 +16,10 @@ func (c *FakeOperatorV1alpha1) CertManagers() v1alpha1.CertManagerInterface { return newFakeCertManagers(c) } +func (c *FakeOperatorV1alpha1) HTTP01Proxies(namespace string) v1alpha1.HTTP01ProxyInterface { + return newFakeHTTP01Proxies(c, namespace) +} + func (c *FakeOperatorV1alpha1) IstioCSRs(namespace string) v1alpha1.IstioCSRInterface { return newFakeIstioCSRs(c, namespace) } diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go index df39e06da..9a6c56b17 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go @@ -4,6 +4,8 @@ package v1alpha1 type CertManagerExpansion interface{} +type HTTP01ProxyExpansion interface{} + type IstioCSRExpansion interface{} type TrustManagerExpansion interface{} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/http01proxy.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/http01proxy.go new file mode 100644 index 000000000..ef636b340 --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/http01proxy.go @@ -0,0 +1,58 @@ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + applyconfigurationsoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + scheme "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// HTTP01ProxiesGetter has a method to return a HTTP01ProxyInterface. +// A group's client should implement this interface. +type HTTP01ProxiesGetter interface { + HTTP01Proxies(namespace string) HTTP01ProxyInterface +} + +// HTTP01ProxyInterface has methods to work with HTTP01Proxy resources. +type HTTP01ProxyInterface interface { + Create(ctx context.Context, hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, opts v1.CreateOptions) (*operatorv1alpha1.HTTP01Proxy, error) + Update(ctx context.Context, hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, opts v1.UpdateOptions) (*operatorv1alpha1.HTTP01Proxy, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, hTTP01Proxy *operatorv1alpha1.HTTP01Proxy, opts v1.UpdateOptions) (*operatorv1alpha1.HTTP01Proxy, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*operatorv1alpha1.HTTP01Proxy, error) + List(ctx context.Context, opts v1.ListOptions) (*operatorv1alpha1.HTTP01ProxyList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *operatorv1alpha1.HTTP01Proxy, err error) + Apply(ctx context.Context, hTTP01Proxy *applyconfigurationsoperatorv1alpha1.HTTP01ProxyApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.HTTP01Proxy, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, hTTP01Proxy *applyconfigurationsoperatorv1alpha1.HTTP01ProxyApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.HTTP01Proxy, err error) + HTTP01ProxyExpansion +} + +// hTTP01Proxies implements HTTP01ProxyInterface +type hTTP01Proxies struct { + *gentype.ClientWithListAndApply[*operatorv1alpha1.HTTP01Proxy, *operatorv1alpha1.HTTP01ProxyList, *applyconfigurationsoperatorv1alpha1.HTTP01ProxyApplyConfiguration] +} + +// newHTTP01Proxies returns a HTTP01Proxies +func newHTTP01Proxies(c *OperatorV1alpha1Client, namespace string) *hTTP01Proxies { + return &hTTP01Proxies{ + gentype.NewClientWithListAndApply[*operatorv1alpha1.HTTP01Proxy, *operatorv1alpha1.HTTP01ProxyList, *applyconfigurationsoperatorv1alpha1.HTTP01ProxyApplyConfiguration]( + "http01proxies", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *operatorv1alpha1.HTTP01Proxy { return &operatorv1alpha1.HTTP01Proxy{} }, + func() *operatorv1alpha1.HTTP01ProxyList { return &operatorv1alpha1.HTTP01ProxyList{} }, + ), + } +} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go index 9eabd32fe..b042f2907 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go @@ -13,6 +13,7 @@ import ( type OperatorV1alpha1Interface interface { RESTClient() rest.Interface CertManagersGetter + HTTP01ProxiesGetter IstioCSRsGetter TrustManagersGetter } @@ -26,6 +27,10 @@ func (c *OperatorV1alpha1Client) CertManagers() CertManagerInterface { return newCertManagers(c) } +func (c *OperatorV1alpha1Client) HTTP01Proxies(namespace string) HTTP01ProxyInterface { + return newHTTP01Proxies(c, namespace) +} + func (c *OperatorV1alpha1Client) IstioCSRs(namespace string) IstioCSRInterface { return newIstioCSRs(c, namespace) } diff --git a/pkg/operator/informers/externalversions/generic.go b/pkg/operator/informers/externalversions/generic.go index 7dc954ca9..a440afd15 100644 --- a/pkg/operator/informers/externalversions/generic.go +++ b/pkg/operator/informers/externalversions/generic.go @@ -39,6 +39,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=operator.openshift.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("certmanagers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().CertManagers().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("http01proxies"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().HTTP01Proxies().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("istiocsrs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().IstioCSRs().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("trustmanagers"): diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/http01proxy.go b/pkg/operator/informers/externalversions/operator/v1alpha1/http01proxy.go new file mode 100644 index 000000000..cf87ef17f --- /dev/null +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/http01proxy.go @@ -0,0 +1,86 @@ +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + apioperatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + versioned "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned" + internalinterfaces "github.com/openshift/cert-manager-operator/pkg/operator/informers/externalversions/internalinterfaces" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/listers/operator/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// HTTP01ProxyInformer provides access to a shared informer and lister for +// HTTP01Proxies. +type HTTP01ProxyInformer interface { + Informer() cache.SharedIndexInformer + Lister() operatorv1alpha1.HTTP01ProxyLister +} + +type hTTP01ProxyInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewHTTP01ProxyInformer constructs a new informer for HTTP01Proxy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewHTTP01ProxyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredHTTP01ProxyInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredHTTP01ProxyInformer constructs a new informer for HTTP01Proxy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredHTTP01ProxyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().HTTP01Proxies(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().HTTP01Proxies(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().HTTP01Proxies(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().HTTP01Proxies(namespace).Watch(ctx, options) + }, + }, + &apioperatorv1alpha1.HTTP01Proxy{}, + resyncPeriod, + indexers, + ) +} + +func (f *hTTP01ProxyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredHTTP01ProxyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *hTTP01ProxyInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apioperatorv1alpha1.HTTP01Proxy{}, f.defaultInformer) +} + +func (f *hTTP01ProxyInformer) Lister() operatorv1alpha1.HTTP01ProxyLister { + return operatorv1alpha1.NewHTTP01ProxyLister(f.Informer().GetIndexer()) +} diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go index 422750840..fbcba0144 100644 --- a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go @@ -10,6 +10,8 @@ import ( type Interface interface { // CertManagers returns a CertManagerInformer. CertManagers() CertManagerInformer + // HTTP01Proxies returns a HTTP01ProxyInformer. + HTTP01Proxies() HTTP01ProxyInformer // IstioCSRs returns a IstioCSRInformer. IstioCSRs() IstioCSRInformer // TrustManagers returns a TrustManagerInformer. @@ -32,6 +34,11 @@ func (v *version) CertManagers() CertManagerInformer { return &certManagerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// HTTP01Proxies returns a HTTP01ProxyInformer. +func (v *version) HTTP01Proxies() HTTP01ProxyInformer { + return &hTTP01ProxyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // IstioCSRs returns a IstioCSRInformer. func (v *version) IstioCSRs() IstioCSRInformer { return &istioCSRInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go index 1692896d0..e76b36ac2 100644 --- a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go +++ b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go @@ -6,6 +6,14 @@ package v1alpha1 // CertManagerLister. type CertManagerListerExpansion interface{} +// HTTP01ProxyListerExpansion allows custom methods to be added to +// HTTP01ProxyLister. +type HTTP01ProxyListerExpansion interface{} + +// HTTP01ProxyNamespaceListerExpansion allows custom methods to be added to +// HTTP01ProxyNamespaceLister. +type HTTP01ProxyNamespaceListerExpansion interface{} + // IstioCSRListerExpansion allows custom methods to be added to // IstioCSRLister. type IstioCSRListerExpansion interface{} diff --git a/pkg/operator/listers/operator/v1alpha1/http01proxy.go b/pkg/operator/listers/operator/v1alpha1/http01proxy.go new file mode 100644 index 000000000..951da193a --- /dev/null +++ b/pkg/operator/listers/operator/v1alpha1/http01proxy.go @@ -0,0 +1,54 @@ +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// HTTP01ProxyLister helps list HTTP01Proxies. +// All objects returned here must be treated as read-only. +type HTTP01ProxyLister interface { + // List lists all HTTP01Proxies in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.HTTP01Proxy, err error) + // HTTP01Proxies returns an object that can list and get HTTP01Proxies. + HTTP01Proxies(namespace string) HTTP01ProxyNamespaceLister + HTTP01ProxyListerExpansion +} + +// hTTP01ProxyLister implements the HTTP01ProxyLister interface. +type hTTP01ProxyLister struct { + listers.ResourceIndexer[*operatorv1alpha1.HTTP01Proxy] +} + +// NewHTTP01ProxyLister returns a new HTTP01ProxyLister. +func NewHTTP01ProxyLister(indexer cache.Indexer) HTTP01ProxyLister { + return &hTTP01ProxyLister{listers.New[*operatorv1alpha1.HTTP01Proxy](indexer, operatorv1alpha1.Resource("http01proxy"))} +} + +// HTTP01Proxies returns an object that can list and get HTTP01Proxies. +func (s *hTTP01ProxyLister) HTTP01Proxies(namespace string) HTTP01ProxyNamespaceLister { + return hTTP01ProxyNamespaceLister{listers.NewNamespaced[*operatorv1alpha1.HTTP01Proxy](s.ResourceIndexer, namespace)} +} + +// HTTP01ProxyNamespaceLister helps list and get HTTP01Proxies. +// All objects returned here must be treated as read-only. +type HTTP01ProxyNamespaceLister interface { + // List lists all HTTP01Proxies in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.HTTP01Proxy, err error) + // Get retrieves the HTTP01Proxy from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*operatorv1alpha1.HTTP01Proxy, error) + HTTP01ProxyNamespaceListerExpansion +} + +// hTTP01ProxyNamespaceLister implements the HTTP01ProxyNamespaceLister +// interface. +type hTTP01ProxyNamespaceLister struct { + listers.ResourceIndexer[*operatorv1alpha1.HTTP01Proxy] +} diff --git a/pkg/operator/setup_manager.go b/pkg/operator/setup_manager.go index d4402a4f8..c902fc358 100644 --- a/pkg/operator/setup_manager.go +++ b/pkg/operator/setup_manager.go @@ -20,11 +20,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" "github.com/openshift/cert-manager-operator/pkg/controller/common" + "github.com/openshift/cert-manager-operator/pkg/controller/http01proxy" "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr" "github.com/openshift/cert-manager-operator/pkg/controller/trustmanager" "github.com/openshift/cert-manager-operator/pkg/version" @@ -79,7 +81,7 @@ var istioCSRManagedResources = []client.Object{ // cert-manager Issuer (and ClusterIssuer, which is never listed here) must not use a // managed-resource label selector: IstioCSR reconciles user-created Issuers referenced // from the spec, which are not labeled by the operator. Those types are left out of -// ByObject so they use the manager cache’s default unfiltered informer per GVK. +// ByObject so they use the manager cache's default unfiltered informer per GVK. var trustManagerManagedResources = []client.Object{ &certmanagerv1.Certificate{}, &appsv1.Deployment{}, @@ -92,6 +94,16 @@ var trustManagerManagedResources = []client.Object{ &admissionregistrationv1.ValidatingWebhookConfiguration{}, } +// http01ProxyManagedResources defines the resources managed by the HTTP01Proxy controller. +// These resources will be watched with a label selector filter. +var http01ProxyManagedResources = []client.Object{ + &appsv1.DaemonSet{}, + &rbacv1.ClusterRole{}, + &rbacv1.ClusterRoleBinding{}, + &corev1.ServiceAccount{}, + &networkingv1.NetworkPolicy{}, +} + func init() { utilruntime.Must(clientscheme.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) @@ -113,6 +125,7 @@ type Manager struct { type ControllerConfig struct { EnableIstioCSR bool EnableTrustManager bool + EnableHTTP01Proxy bool } // NewControllerManager creates a unified manager for all enabled operand controllers. @@ -120,7 +133,7 @@ type ControllerConfig struct { func NewControllerManager(config ControllerConfig) (*Manager, error) { setupLog.Info("setting up unified operator manager") setupLog.Info("controller", "version", version.Get()) - setupLog.Info("enabled controllers", "istioCSR", config.EnableIstioCSR, "trustManager", config.EnableTrustManager) + setupLog.Info("enabled controllers", "istioCSR", config.EnableIstioCSR, "trustManager", config.EnableTrustManager, "http01Proxy", config.EnableHTTP01Proxy) cacheBuilder := newUnifiedCacheBuilder(config) @@ -128,6 +141,9 @@ func NewControllerManager(config ControllerConfig) (*Manager, error) { Scheme: scheme, NewCache: cacheBuilder, Logger: ctrl.Log.WithName("operator-manager"), + // Use a separate port for the controller-runtime metrics server to avoid + // conflicting with the library-go metrics server on :8080. + Metrics: metricsserver.Options{BindAddress: ":8085"}, }) if err != nil { return nil, fmt.Errorf("failed to create manager: %w", err) @@ -146,6 +162,12 @@ func NewControllerManager(config ControllerConfig) (*Manager, error) { } } + if config.EnableHTTP01Proxy { + if err := setupHTTP01ProxyController(mgr); err != nil { + return nil, err + } + } + return &Manager{ manager: mgr, }, nil @@ -177,6 +199,19 @@ func setupTrustManagerController(mgr ctrl.Manager) error { return nil } +// setupHTTP01ProxyController creates and registers the HTTP01Proxy controller with the manager. +func setupHTTP01ProxyController(mgr ctrl.Manager) error { + setupLog.Info("setting up controller", "name", http01proxy.ControllerName) + r, err := http01proxy.New(mgr) + if err != nil { + return fmt.Errorf("failed to create %s reconciler object: %w", http01proxy.ControllerName, err) + } + if err := r.SetupWithManager(mgr); err != nil { + return fmt.Errorf("failed to create %s controller: %w", http01proxy.ControllerName, err) + } + return nil +} + // newUnifiedCacheBuilder creates a cache builder that combines cache configurations // for all enabled controllers into a single unified cache. func newUnifiedCacheBuilder(config ControllerConfig) cache.NewCacheFunc { @@ -212,6 +247,14 @@ func buildCacheObjectList(config ControllerConfig) (map[client.Object]cache.ByOb objectList[&v1alpha1.TrustManager{}] = cache.ByObject{} } + if config.EnableHTTP01Proxy { + if err := addControllerCacheConfig(objectList, http01proxy.RequestEnqueueLabelValue, http01ProxyManagedResources); err != nil { + return nil, fmt.Errorf("failed to configure HTTP01Proxy cache: %w", err) + } + // HTTP01Proxy CR - no label filter needed + objectList[&v1alpha1.HTTP01Proxy{}] = cache.ByObject{} + } + return objectList, nil } diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index dc1541c9d..b51989fc6 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -18,6 +18,7 @@ import ( "github.com/openshift/library-go/pkg/operator/status" "github.com/openshift/library-go/pkg/operator/v1helpers" + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" "github.com/openshift/cert-manager-operator/pkg/controller/certmanager" "github.com/openshift/cert-manager-operator/pkg/features" certmanoperatorclient "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned" @@ -144,12 +145,14 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error } istioCSREnabled := features.IsIstioCSRFeatureGateEnabled() trustManagerEnabled := featureStatus.IsTrustManagerFeatureGateEnabled() + http01ProxyEnabled := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureHTTP01Proxy) - if istioCSREnabled || trustManagerEnabled { + if istioCSREnabled || trustManagerEnabled || http01ProxyEnabled { // Create unified manager for all enabled operand controllers manager, err := NewControllerManager(ControllerConfig{ EnableIstioCSR: istioCSREnabled, EnableTrustManager: trustManagerEnabled, + EnableHTTP01Proxy: http01ProxyEnabled, }) if err != nil { return fmt.Errorf("failed to create unified controller manager: %w", err)