Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/name: cert-manager-operator
config.openshift.io/inject-trusted-cabundle: "true"
name: cert-manager-operator-trusted-ca-bundle
7 changes: 7 additions & 0 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ images:
- name: controller
newName: openshift.io/cert-manager-operator
newTag: latest
configMapGenerator:
- name: trusted-ca-bundle
options:
disableNameSuffixHash: true
labels:
app.kubernetes.io/name: cert-manager-operator
config.openshift.io/inject-trusted-cabundle: "true"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
k8s.io/kubernetes v1.34.4
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
sigs.k8s.io/controller-runtime v0.22.4
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.1
sigs.k8s.io/yaml v1.6.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 h
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
31 changes: 13 additions & 18 deletions go.work.sum

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions pkg/controller/common/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package common

// ManagedResourceLabelKey is the common label key used by all operand controllers
// to identify resources they manage. Each controller uses a different value
// to distinguish its resources.
const ManagedResourceLabelKey = "app"
const (
// ManagedResourceLabelKey is the common label key used by all operand controllers
// to identify resources they manage. Each controller uses a different value
// to distinguish its resources.
ManagedResourceLabelKey = "app"

// OperatorNamespace is the namespace where cert-manager-operator runs.
OperatorNamespace = "cert-manager-operator"

// TrustedCABundleConfigMapName is the ConfigMap in the operator namespace
// where CNO injects the cluster's trusted CA bundle.
TrustedCABundleConfigMapName = "cert-manager-operator-trusted-ca-bundle"

// TrustedCABundleKey is the key in the CNO-injected ConfigMap that contains the CA bundle PEM.
TrustedCABundleKey = "ca-bundle.crt"
)
146 changes: 146 additions & 0 deletions pkg/controller/trustmanager/configmaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package trustmanager

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"maps"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/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"
)

// caPackage represents the JSON format expected by trust-manager
type caPackage struct {
Name string `json:"name"`
Bundle string `json:"bundle"`
Version string `json:"version"`
}

// createOrApplyDefaultCAPackageConfigMap reads the CNO-injected CA bundle,
// formats it into trust-manager's expected JSON package format, and creates
// or updates the package ConfigMap in the operand namespace.
// Returns the SHA-256 hash of the CA bundle content and any error.
// Returns ("", nil) when defaultCAPackage is disabled.
func (r *Reconciler) createOrApplyDefaultCAPackageConfigMap(trustManager *v1alpha1.TrustManager, resourceLabels, resourceAnnotations map[string]string) (string, error) {
if !defaultCAPackageEnabled(trustManager.Spec.TrustManagerConfig.DefaultCAPackage) {
return "", nil
}

caBundle, resourceVersion, err := r.readTrustedCABundle()
if err != nil {
return "", err
}

pkgJSON, err := formatCAPackage(caBundle, resourceVersion)
if err != nil {
return "", err
}

bundleHash := computeCABundleHash(caBundle)

desired := buildDefaultCAPackageConfigMap(pkgJSON, resourceLabels, resourceAnnotations)

cmName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName())
r.log.V(4).Info("reconciling default CA package ConfigMap", "name", cmName)

existing := &corev1.ConfigMap{}
exists, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), existing)
if err != nil {
return "", common.FromClientError(err, "failed to check if ConfigMap %q exists", cmName)
}
if exists && !configMapModified(desired, existing) {
r.log.V(4).Info("default CA package ConfigMap exists and is in desired state", "name", cmName)
return bundleHash, nil
}

r.log.V(2).Info("default CA package ConfigMap has been modified, updating to desired state", "name", cmName)
if err := r.Patch(r.ctx, desired, client.Apply, client.FieldOwner(fieldOwner), client.ForceOwnership); err != nil {
return "", common.FromClientError(err, "failed to apply ConfigMap %q", cmName)
}

r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "default CA package ConfigMap %s applied", cmName)
return bundleHash, nil
}

// readTrustedCABundle reads the CNO-injected CA bundle from the operator namespace.
// Returns the PEM bundle, the ConfigMap's resource version, and any error.
func (r *Reconciler) readTrustedCABundle() (string, string, error) {
injectionCM := &corev1.ConfigMap{}
key := client.ObjectKey{
Namespace: common.OperatorNamespace,
Name: common.TrustedCABundleConfigMapName,
}
if err := r.Get(r.ctx, key, injectionCM); err != nil {
return "", "", common.FromClientError(
err,
"failed to read CA bundle ConfigMap %q in namespace %q",
common.TrustedCABundleConfigMapName, common.OperatorNamespace,
)
}

caBundle, ok := injectionCM.Data[common.TrustedCABundleKey]
if !ok || caBundle == "" {
return "", "", common.FromClientError(
fmt.Errorf("CA bundle ConfigMap %q does not contain key %q", common.TrustedCABundleConfigMapName, common.TrustedCABundleKey),
"missing key %q in ConfigMap %q", common.TrustedCABundleKey, common.TrustedCABundleConfigMapName,
)
}

return caBundle, injectionCM.ResourceVersion, nil
}

// formatCAPackage encodes the CA bundle into trust-manager's expected JSON package format.
func formatCAPackage(caBundle, version string) ([]byte, error) {
pkg := caPackage{
Name: defaultCAPackageName,
Bundle: caBundle,
Version: version,
}
// TODO: cross check the formatting of the JSON package
pkgJSON, err := json.Marshal(pkg)
if err != nil {
return nil, fmt.Errorf("failed to marshal CA package to JSON: %w", err)
}
return pkgJSON, nil
}

// computeCABundleHash returns the SHA-256 hash of the CA bundle PEM content.
// The hash is used as the pod template annotation to trigger rolling restarts
// only when the actual certificate content changes.
func computeCABundleHash(caBundle string) string {
hash := sha256.Sum256([]byte(caBundle))
return hex.EncodeToString(hash[:])
}

// buildDefaultCAPackageConfigMap constructs the desired ConfigMap for the
// formatted CA package in the operand namespace.
func buildDefaultCAPackageConfigMap(pkgJSON []byte, resourceLabels, resourceAnnotations map[string]string) *corev1.ConfigMap {
cm := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: defaultCAPackageConfigMapName,
Namespace: operandNamespace,
},
Data: map[string]string{
defaultCAPackageFilename: string(pkgJSON),
},
}
common.UpdateResourceLabels(cm, resourceLabels)
updateResourceAnnotations(cm, resourceAnnotations)
return cm
}

// configMapModified checks whether the desired ConfigMap differs from the existing one.
func configMapModified(desired, existing *corev1.ConfigMap) bool {
return managedMetadataModified(desired, existing) ||
!maps.Equal(desired.Data, existing.Data)
}
Loading