diff --git a/go.mod b/go.mod index c42a6035c1..52d908c94f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/openshift/api v0.0.0-20250710004639-926605d3338b github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee github.com/openshift/client-go v0.0.0-20250710075018-396b36f983ee - github.com/openshift/library-go v0.0.0-20250710130336-73c7662bc565 + github.com/openshift/library-go v0.0.0-20250724123005-03d85c4e997c github.com/pkg/profile v1.7.0 // indirect github.com/prometheus/client_golang v1.22.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 2df9924a78..1fdb371e3e 100644 --- a/go.sum +++ b/go.sum @@ -166,8 +166,8 @@ github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+S github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= github.com/openshift/client-go v0.0.0-20250710075018-396b36f983ee h1:tOtrrxfDEW8hK3eEsHqxsXurq/D6LcINGfprkQC3hqY= github.com/openshift/client-go v0.0.0-20250710075018-396b36f983ee/go.mod h1:zhRiYyNMk89llof2qEuGPWPD+joQPhCRUc2IK0SB510= -github.com/openshift/library-go v0.0.0-20250710130336-73c7662bc565 h1:DtyzonCpVZxqYp4rp2cCRwBTEXZWw5fX9YE0tCM5hi8= -github.com/openshift/library-go v0.0.0-20250710130336-73c7662bc565/go.mod h1:tptKNust9MdRI0p90DoBSPHIrBa9oh+Rok59tF0vT8c= +github.com/openshift/library-go v0.0.0-20250724123005-03d85c4e997c h1:KG1U3r4ocDe39/Mvc6oxgDoi9YlCrWAor6n+uvJzRok= +github.com/openshift/library-go v0.0.0-20250724123005-03d85c4e997c/go.mod h1:tptKNust9MdRI0p90DoBSPHIrBa9oh+Rok59tF0vT8c= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go index a98106ff58..6909860582 100644 --- a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go +++ b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go @@ -14,6 +14,7 @@ import ( "github.com/openshift/cluster-kube-apiserver-operator/bindata" "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/operatorclient" "github.com/openshift/library-go/pkg/controller/factory" + "github.com/openshift/library-go/pkg/operator/certrotation" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" "github.com/openshift/library-go/pkg/operator/resource/resourceread" @@ -29,10 +30,10 @@ const workQueueKey = "key" type NodeKubeconfigController struct { operatorClient v1helpers.StaticPodOperatorClient - kubeClient kubernetes.Interface - configMapLister corev1listers.ConfigMapLister - secretLister corev1listers.SecretLister - infrastuctureLister configv1listers.InfrastructureLister + kubeClient kubernetes.Interface + configMapLister corev1listers.ConfigMapLister + secretLister corev1listers.SecretLister + infrastructureLister configv1listers.InfrastructureLister } func NewNodeKubeconfigController( @@ -43,11 +44,11 @@ func NewNodeKubeconfigController( eventRecorder events.Recorder, ) factory.Controller { c := &NodeKubeconfigController{ - operatorClient: operatorClient, - kubeClient: kubeClient, - configMapLister: kubeInformersForNamespaces.ConfigMapLister(), - secretLister: kubeInformersForNamespaces.SecretLister(), - infrastuctureLister: infrastuctureInformer.Lister(), + operatorClient: operatorClient, + kubeClient: kubeClient, + configMapLister: kubeInformersForNamespaces.ConfigMapLister(), + secretLister: kubeInformersForNamespaces.SecretLister(), + infrastructureLister: infrastuctureInformer.Lister(), } return factory.New().WithInformers( @@ -85,7 +86,7 @@ func (c NodeKubeconfigController) sync(ctx context.Context, syncContext factory. c.kubeClient.CoreV1(), c.secretLister, c.configMapLister, - c.infrastuctureLister, + c.infrastructureLister, syncContext.Recorder(), ) if err != nil { @@ -152,6 +153,13 @@ func ensureNodeKubeconfigs(ctx context.Context, client coreclientv1.CoreV1Interf requiredSecret.Annotations = map[string]string{} } requiredSecret.Annotations[annotations.OpenShiftComponent] = "kube-apiserver" + // Copy not-before/not-after annotations from systemAdminClientCert + if len(systemAdminCredsSecret.Annotations[certrotation.CertificateNotBeforeAnnotation]) > 0 { + requiredSecret.Annotations[certrotation.CertificateNotBeforeAnnotation] = systemAdminCredsSecret.Annotations[certrotation.CertificateNotBeforeAnnotation] + } + if len(systemAdminCredsSecret.Annotations[certrotation.CertificateNotAfterAnnotation]) > 0 { + requiredSecret.Annotations[certrotation.CertificateNotAfterAnnotation] = systemAdminCredsSecret.Annotations[certrotation.CertificateNotAfterAnnotation] + } _, _, err = resourceapply.ApplySecret(ctx, client, recorder, requiredSecret) if err != nil { diff --git a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go index cdca5acbd5..341af79e90 100644 --- a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go +++ b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go @@ -2,12 +2,15 @@ package nodekubeconfigcontroller import ( "context" + "encoding/base64" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/openshift/api/annotations" configv1 "github.com/openshift/api/config/v1" configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" + "github.com/openshift/library-go/pkg/operator/certrotation" "github.com/openshift/library-go/pkg/operator/events" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -85,6 +88,44 @@ func (l *secretLister) Get(name string) (*corev1.Secret, error) { return l.client.CoreV1().Secrets(l.namespace).Get(context.Background(), name, metav1.GetOptions{}) } +const privateKey = "fake private key" // notsecret +const publicKey = "fake public key" +const certNotBefore = "2024-11-26T08:50:46Z" +const certNotAfter = "2034-11-24T08:50:46Z" +const lbExtServer = "https://lb-ext.test:6443" +const lbIntServer = "https://lb-int.test:6443" + +var publicKeyBase64 = base64.StdEncoding.EncodeToString([]byte(publicKey)) +var privateKeyBase64 = base64.StdEncoding.EncodeToString([]byte(privateKey)) + +func generateKubeConfig(name, server string) []byte { + // localhost-recovery is a special case, it also has tls-server-name set + tlsServerName := "" + if name == "localhost-recovery" { + tlsServerName = ` + tls-server-name: localhost-recovery` + } + return []byte(fmt.Sprintf(`apiVersion: v1 +kind: Config +clusters: +- cluster: + certificate-authority-data: a3ViZS1hcGlzZXJ2ZXItc2VydmVyLWNhIGNlcnRpZmljYXRl + server: %s%s + name: %s +contexts: +- context: + cluster: %s + user: system:admin + name: system:admin +current-context: system:admin +users: +- name: system:admin + user: + client-certificate-data: %s + client-key-data: %s +`, server, tlsServerName, name, name, publicKeyBase64, privateKeyBase64)) +} + func TestEnsureNodeKubeconfigs(t *testing.T) { tt := []struct { name string @@ -109,10 +150,14 @@ func TestEnsureNodeKubeconfigs(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "openshift-kube-apiserver-operator", Name: "node-system-admin-client", + Annotations: map[string]string{ + certrotation.CertificateNotBeforeAnnotation: certNotBefore, + certrotation.CertificateNotAfterAnnotation: certNotAfter, + }, }, Data: map[string][]byte{ - "tls.crt": []byte("system:admin certificate"), - "tls.key": []byte("system:admin key"), + "tls.crt": []byte(publicKey), + "tls.key": []byte(privateKey), }, }, }, @@ -122,8 +167,72 @@ func TestEnsureNodeKubeconfigs(t *testing.T) { Name: "cluster", }, Status: configv1.InfrastructureStatus{ - APIServerURL: "https://lb-ext.test:6443", - APIServerInternalURL: "https://lb-int.test:6443", + APIServerURL: lbExtServer, + APIServerInternalURL: lbIntServer, + }, + }, + expectedErr: nil, + expectedActions: []clienttesting.Action{ + clienttesting.CreateActionImpl{ + ActionImpl: clienttesting.ActionImpl{ + Namespace: "openshift-kube-apiserver", + Verb: "create", + Resource: corev1.SchemeGroupVersion.WithResource("secrets"), + }, + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "node-kubeconfigs", + Annotations: map[string]string{ + annotations.OpenShiftComponent: "kube-apiserver", + certrotation.CertificateNotBeforeAnnotation: certNotBefore, + certrotation.CertificateNotAfterAnnotation: certNotAfter, + }, + }, + Data: map[string][]byte{ + "localhost.kubeconfig": generateKubeConfig("localhost", "https://localhost:6443"), + "localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"), + "lb-ext.kubeconfig": generateKubeConfig("lb-ext", lbExtServer), + "lb-int.kubeconfig": generateKubeConfig("lb-int", lbIntServer), + }, + }, + }, + }, + }, { + name: "no annotations set", + existingObjects: []runtime.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "kube-apiserver-server-ca", + }, + Data: map[string]string{ + "ca-bundle.crt": "kube-apiserver-server-ca certificate", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver-operator", + Name: "node-system-admin-client", + }, + Data: map[string][]byte{ + "tls.crt": []byte(publicKey), + "tls.key": []byte(privateKey), + }, + }, + }, + infrastructure: &configv1.Infrastructure{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{ + APIServerURL: lbExtServer, + APIServerInternalURL: lbIntServer, }, }, expectedErr: nil, @@ -147,84 +256,111 @@ func TestEnsureNodeKubeconfigs(t *testing.T) { }, }, Data: map[string][]byte{ - "localhost.kubeconfig": []byte(`apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: a3ViZS1hcGlzZXJ2ZXItc2VydmVyLWNhIGNlcnRpZmljYXRl - server: https://localhost:6443 - name: localhost -contexts: -- context: - cluster: localhost - user: system:admin - name: system:admin -current-context: system:admin -users: -- name: system:admin - user: - client-certificate-data: c3lzdGVtOmFkbWluIGNlcnRpZmljYXRl - client-key-data: c3lzdGVtOmFkbWluIGtleQ== -`), - "localhost-recovery.kubeconfig": []byte(`apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: a3ViZS1hcGlzZXJ2ZXItc2VydmVyLWNhIGNlcnRpZmljYXRl - server: https://localhost:6443 - tls-server-name: localhost-recovery - name: localhost-recovery -contexts: -- context: - cluster: localhost-recovery - user: system:admin - name: system:admin -current-context: system:admin -users: -- name: system:admin - user: - client-certificate-data: c3lzdGVtOmFkbWluIGNlcnRpZmljYXRl - client-key-data: c3lzdGVtOmFkbWluIGtleQ== -`), - "lb-ext.kubeconfig": []byte(`apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: a3ViZS1hcGlzZXJ2ZXItc2VydmVyLWNhIGNlcnRpZmljYXRl - server: https://lb-ext.test:6443 - name: lb-ext -contexts: -- context: - cluster: lb-ext - user: system:admin - name: system:admin -current-context: system:admin -users: -- name: system:admin - user: - client-certificate-data: c3lzdGVtOmFkbWluIGNlcnRpZmljYXRl - client-key-data: c3lzdGVtOmFkbWluIGtleQ== -`), - "lb-int.kubeconfig": []byte(`apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: a3ViZS1hcGlzZXJ2ZXItc2VydmVyLWNhIGNlcnRpZmljYXRl - server: https://lb-int.test:6443 - name: lb-int -contexts: -- context: - cluster: lb-int - user: system:admin - name: system:admin -current-context: system:admin -users: -- name: system:admin - user: - client-certificate-data: c3lzdGVtOmFkbWluIGNlcnRpZmljYXRl - client-key-data: c3lzdGVtOmFkbWluIGtleQ== -`), + "localhost.kubeconfig": generateKubeConfig("localhost", "https://localhost:6443"), + "localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"), + "lb-ext.kubeconfig": generateKubeConfig("lb-ext", lbExtServer), + "lb-int.kubeconfig": generateKubeConfig("lb-int", lbIntServer), + }, + }, + }, + }, + }, { + name: "update", + existingObjects: []runtime.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "kube-apiserver-server-ca", + }, + Data: map[string]string{ + "ca-bundle.crt": "kube-apiserver-server-ca certificate", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver-operator", + Name: "node-system-admin-client", + Annotations: map[string]string{ + certrotation.CertificateNotBeforeAnnotation: certNotBefore, + certrotation.CertificateNotAfterAnnotation: certNotAfter, + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(publicKey), + "tls.key": []byte(privateKey), + }, + }, + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "node-kubeconfigs", + Annotations: map[string]string{ + annotations.OpenShiftComponent: "kube-apiserver", + certrotation.CertificateNotBeforeAnnotation: "some-old-not-before", + certrotation.CertificateNotAfterAnnotation: "some-old-not-after", + }, + }, + Data: map[string][]byte{ + "localhost.kubeconfig": []byte("invalid kubeconfig"), + "localhost-recovery.kubeconfig": []byte("another invalid kubeconfig"), + "lb-ext.kubeconfig": []byte("more invalid kubeconfig"), + "lb-int.kubeconfig": []byte("even more invalid kubeconfig"), + }, + }, + }, + infrastructure: &configv1.Infrastructure{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{ + APIServerURL: lbExtServer, + APIServerInternalURL: lbIntServer, + }, + }, + expectedErr: nil, + expectedActions: []clienttesting.Action{ + clienttesting.DeleteActionImpl{ + ActionImpl: clienttesting.ActionImpl{ + Namespace: "openshift-kube-apiserver", + Verb: "delete", + Resource: corev1.SchemeGroupVersion.WithResource("secrets"), + }, + Name: "node-kubeconfigs", + }, + clienttesting.CreateActionImpl{ + ActionImpl: clienttesting.ActionImpl{ + Namespace: "openshift-kube-apiserver", + Verb: "create", + Resource: corev1.SchemeGroupVersion.WithResource("secrets"), + }, + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "node-kubeconfigs", + Labels: map[string]string{}, + OwnerReferences: []metav1.OwnerReference{}, + Annotations: map[string]string{ + annotations.OpenShiftComponent: "kube-apiserver", + certrotation.CertificateNotBeforeAnnotation: certNotBefore, + certrotation.CertificateNotAfterAnnotation: certNotAfter, + }, + }, + Data: map[string][]byte{ + "localhost.kubeconfig": generateKubeConfig("localhost", "https://localhost:6443"), + "localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"), + "lb-ext.kubeconfig": generateKubeConfig("lb-ext", lbExtServer), + "lb-int.kubeconfig": generateKubeConfig("lb-int", lbIntServer), }, + Type: corev1.SecretTypeOpaque, }, }, }, diff --git a/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go b/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go index 80f5efc2c0..33a09ae16e 100644 --- a/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go +++ b/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go @@ -629,7 +629,7 @@ func MakeSelfSignedCAConfig(name string, lifetime time.Duration) (*TLSCertificat func MakeSelfSignedCAConfigForSubject(subject pkix.Name, lifetime time.Duration) (*TLSCertificateConfig, error) { if lifetime <= 0 { lifetime = DefaultCACertificateLifetimeDuration - fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %d years!\n", subject.CommonName, lifetime) + fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %s!\n", subject.CommonName, lifetime.String()) } if lifetime > DefaultCACertificateLifetimeDuration { @@ -1018,7 +1018,7 @@ func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time func newServerCertificateTemplate(subject pkix.Name, hosts []string, lifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate { if lifetime <= 0 { lifetime = DefaultCertificateLifetimeDuration - fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %d years!\n", subject.CommonName, lifetime) + fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %s!\n", subject.CommonName, lifetime.String()) } if lifetime > DefaultCertificateLifetimeDuration { @@ -1105,7 +1105,7 @@ func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { func NewClientCertificateTemplate(subject pkix.Name, lifetime time.Duration, currentTime func() time.Time) *x509.Certificate { if lifetime <= 0 { lifetime = DefaultCertificateLifetimeDuration - fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %d years!\n", subject.CommonName, lifetime) + fmt.Fprintf(os.Stderr, "Validity period of the certificate for %q is unset, resetting to %s!\n", subject.CommonName, lifetime.String()) } if lifetime > DefaultCertificateLifetimeDuration { diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/annotations.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/annotations.go index c7c3356305..3c051c88e6 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/annotations.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/annotations.go @@ -16,9 +16,13 @@ const ( CertificateIssuer = "auth.openshift.io/certificate-issuer" // CertificateHostnames contains the hostnames used by a signer. CertificateHostnames = "auth.openshift.io/certificate-hostnames" - // AutoRegenerateAfterOfflineExpiryAnnotation contains a link to PR and an e2e test name which verifies + // CertificateTestNameAnnotation is an e2e test name which verifies that TLS artifact is created and used correctly + CertificateTestNameAnnotation string = "certificates.openshift.io/test-name" + // CertificateAutoRegenerateAfterOfflineExpiryAnnotation contains a link to PR adding this annotation which verifies // that TLS artifact is correctly regenerated after it has expired - AutoRegenerateAfterOfflineExpiryAnnotation string = "certificates.openshift.io/auto-regenerate-after-offline-expiry" + CertificateAutoRegenerateAfterOfflineExpiryAnnotation string = "certificates.openshift.io/auto-regenerate-after-offline-expiry" + // CertificateRefreshPeriodAnnotation is the interval at which the certificate should be refreshed. + CertificateRefreshPeriodAnnotation string = "certificates.openshift.io/refresh-period" ) type AdditionalAnnotations struct { @@ -26,13 +30,16 @@ type AdditionalAnnotations struct { JiraComponent string // Description is a human-readable one sentence description of certificate purpose Description string - // AutoRegenerateAfterOfflineExpiry contains a link to PR and an e2e test name which verifies - // that TLS artifact is correctly regenerated after it has expired + // TestName is an e2e test name which verifies that TLS artifact is created and used correctly + TestName string + // AutoRegenerateAfterOfflineExpiry contains a link to PR which adds this annotation on the TLS artifact AutoRegenerateAfterOfflineExpiry string // NotBefore contains certificate the certificate creation date in RFC3339 format. NotBefore string // NotAfter contains certificate the certificate validity date in RFC3339 format. NotAfter string + // RefreshPeriod contains the interval at which the certificate should be refreshed. + RefreshPeriod string } func (a AdditionalAnnotations) EnsureTLSMetadataUpdate(meta *metav1.ObjectMeta) bool { @@ -42,30 +49,46 @@ func (a AdditionalAnnotations) EnsureTLSMetadataUpdate(meta *metav1.ObjectMeta) } if len(a.JiraComponent) > 0 && meta.Annotations[annotations.OpenShiftComponent] != a.JiraComponent { diff := cmp.Diff(meta.Annotations[annotations.OpenShiftComponent], a.JiraComponent) - klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", annotations.OpenShiftComponent, meta.Name, meta.Namespace, diff) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", annotations.OpenShiftComponent, meta.Namespace, meta.Name, diff) meta.Annotations[annotations.OpenShiftComponent] = a.JiraComponent modified = true } if len(a.Description) > 0 && meta.Annotations[annotations.OpenShiftDescription] != a.Description { diff := cmp.Diff(meta.Annotations[annotations.OpenShiftDescription], a.Description) - klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", annotations.OpenShiftDescription, meta.Name, meta.Namespace, diff) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", annotations.OpenShiftDescription, meta.Namespace, meta.Name, diff) meta.Annotations[annotations.OpenShiftDescription] = a.Description modified = true } - if len(a.AutoRegenerateAfterOfflineExpiry) > 0 && meta.Annotations[AutoRegenerateAfterOfflineExpiryAnnotation] != a.AutoRegenerateAfterOfflineExpiry { - diff := cmp.Diff(meta.Annotations[AutoRegenerateAfterOfflineExpiryAnnotation], a.AutoRegenerateAfterOfflineExpiry) - klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", AutoRegenerateAfterOfflineExpiryAnnotation, meta.Name, meta.Namespace, diff) - meta.Annotations[AutoRegenerateAfterOfflineExpiryAnnotation] = a.AutoRegenerateAfterOfflineExpiry + if len(a.TestName) > 0 && meta.Annotations[CertificateTestNameAnnotation] != a.TestName { + diff := cmp.Diff(meta.Annotations[CertificateTestNameAnnotation], a.TestName) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", CertificateTestNameAnnotation, meta.Name, meta.Namespace, diff) + meta.Annotations[CertificateTestNameAnnotation] = a.TestName + modified = true + } + if len(a.AutoRegenerateAfterOfflineExpiry) > 0 && meta.Annotations[CertificateAutoRegenerateAfterOfflineExpiryAnnotation] != a.AutoRegenerateAfterOfflineExpiry { + diff := cmp.Diff(meta.Annotations[CertificateAutoRegenerateAfterOfflineExpiryAnnotation], a.AutoRegenerateAfterOfflineExpiry) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", CertificateAutoRegenerateAfterOfflineExpiryAnnotation, meta.Namespace, meta.Name, diff) + meta.Annotations[CertificateAutoRegenerateAfterOfflineExpiryAnnotation] = a.AutoRegenerateAfterOfflineExpiry modified = true } if len(a.NotBefore) > 0 && meta.Annotations[CertificateNotBeforeAnnotation] != a.NotBefore { + diff := cmp.Diff(meta.Annotations[CertificateNotBeforeAnnotation], a.NotBefore) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", CertificateNotBeforeAnnotation, meta.Name, meta.Namespace, diff) meta.Annotations[CertificateNotBeforeAnnotation] = a.NotBefore modified = true } if len(a.NotAfter) > 0 && meta.Annotations[CertificateNotAfterAnnotation] != a.NotAfter { + diff := cmp.Diff(meta.Annotations[CertificateNotAfterAnnotation], a.NotAfter) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", CertificateNotAfterAnnotation, meta.Name, meta.Namespace, diff) meta.Annotations[CertificateNotAfterAnnotation] = a.NotAfter modified = true } + if len(a.RefreshPeriod) > 0 && meta.Annotations[CertificateRefreshPeriodAnnotation] != a.RefreshPeriod { + diff := cmp.Diff(meta.Annotations[CertificateRefreshPeriodAnnotation], a.RefreshPeriod) + klog.V(2).Infof("Updating %q annotation for %s/%s, diff: %s", CertificateRefreshPeriodAnnotation, meta.Name, meta.Namespace, diff) + meta.Annotations[CertificateRefreshPeriodAnnotation] = a.RefreshPeriod + modified = true + } return modified } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/metadata.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/metadata.go index f64bde8fe0..1764a63552 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/metadata.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/metadata.go @@ -5,7 +5,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func ensureMetadataUpdate(secret *corev1.Secret, owner *metav1.OwnerReference, additionalAnnotations AdditionalAnnotations) bool { +func ensureOwnerRefAndTLSAnnotations(secret *corev1.Secret, owner *metav1.OwnerReference, additionalAnnotations AdditionalAnnotations) bool { needsMetadataUpdate := false // no ownerReference set if owner != nil { diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go index c2b300858e..1cb4e55542 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go @@ -80,7 +80,7 @@ func (c RotatedSigningCASecret) EnsureSigningCertKeyPair(ctx context.Context) (* // run Update if metadata needs changing unless we're in RefreshOnlyWhenExpired mode if !c.RefreshOnlyWhenExpired { - needsMetadataUpdate := ensureMetadataUpdate(signingCertKeyPairSecret, c.Owner, c.AdditionalAnnotations) + needsMetadataUpdate := ensureOwnerRefAndTLSAnnotations(signingCertKeyPairSecret, c.Owner, c.AdditionalAnnotations) needsTypeChange := ensureSecretTLSTypeSet(signingCertKeyPairSecret) updateRequired = needsMetadataUpdate || needsTypeChange } @@ -92,7 +92,7 @@ func (c RotatedSigningCASecret) EnsureSigningCertKeyPair(ctx context.Context) (* reason = "secret doesn't exist" } c.EventRecorder.Eventf("SignerUpdateRequired", "%q in %q requires a new signing cert/key pair: %v", c.Name, c.Namespace, reason) - if err := setSigningCertKeyPairSecret(signingCertKeyPairSecret, c.Validity, c.AdditionalAnnotations); err != nil { + if err = setSigningCertKeyPairSecretAndTLSAnnotations(signingCertKeyPairSecret, c.Validity, c.Refresh, c.AdditionalAnnotations); err != nil { return nil, false, err } @@ -199,18 +199,30 @@ func getValidityFromAnnotations(annotations map[string]string) (notBefore time.T return notBefore, notAfter, "" } +// setSigningCertKeyPairSecretAndTLSAnnotations generates a new signing certificate and key pair, +// stores them in the specified secret, and adds predefined TLS annotations to that secret. +func setSigningCertKeyPairSecretAndTLSAnnotations(signingCertKeyPairSecret *corev1.Secret, validity, refresh time.Duration, tlsAnnotations AdditionalAnnotations) error { + ca, err := setSigningCertKeyPairSecret(signingCertKeyPairSecret, validity) + if err != nil { + return err + } + + setTLSAnnotationsOnSigningCertKeyPairSecret(signingCertKeyPairSecret, ca, refresh, tlsAnnotations) + return nil +} + // setSigningCertKeyPairSecret creates a new signing cert/key pair and sets them in the secret -func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validity time.Duration, annotations AdditionalAnnotations) error { +func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validity time.Duration) (*crypto.TLSCertificateConfig, error) { signerName := fmt.Sprintf("%s_%s@%d", signingCertKeyPairSecret.Namespace, signingCertKeyPairSecret.Name, time.Now().Unix()) ca, err := crypto.MakeSelfSignedCAConfigForDuration(signerName, validity) if err != nil { - return err + return nil, err } certBytes := &bytes.Buffer{} keyBytes := &bytes.Buffer{} - if err := ca.WriteCertConfig(certBytes, keyBytes); err != nil { - return err + if err = ca.WriteCertConfig(certBytes, keyBytes); err != nil { + return nil, err } if signingCertKeyPairSecret.Annotations == nil { @@ -221,11 +233,21 @@ func setSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, validi } signingCertKeyPairSecret.Data["tls.crt"] = certBytes.Bytes() signingCertKeyPairSecret.Data["tls.key"] = keyBytes.Bytes() - annotations.NotBefore = ca.Certs[0].NotBefore.Format(time.RFC3339) - annotations.NotAfter = ca.Certs[0].NotAfter.Format(time.RFC3339) - signingCertKeyPairSecret.Annotations[CertificateIssuer] = ca.Certs[0].Issuer.CommonName + return ca, nil +} - _ = annotations.EnsureTLSMetadataUpdate(&signingCertKeyPairSecret.ObjectMeta) +// setTLSAnnotationsOnSigningCertKeyPairSecret applies predefined TLS annotations to the given secret. +// +// This function does not perform nil checks on its parameters and assumes that the +// secret's Annotations field has already been initialized. +// +// These assumptions are safe because this function is only called after the secret +// has been initialized in setSigningCertKeyPairSecret. +func setTLSAnnotationsOnSigningCertKeyPairSecret(signingCertKeyPairSecret *corev1.Secret, ca *crypto.TLSCertificateConfig, refresh time.Duration, tlsAnnotations AdditionalAnnotations) { + signingCertKeyPairSecret.Annotations[CertificateIssuer] = ca.Certs[0].Issuer.CommonName - return nil + tlsAnnotations.NotBefore = ca.Certs[0].NotBefore.Format(time.RFC3339) + tlsAnnotations.NotAfter = ca.Certs[0].NotAfter.Format(time.RFC3339) + tlsAnnotations.RefreshPeriod = refresh.String() + _ = tlsAnnotations.EnsureTLSMetadataUpdate(&signingCertKeyPairSecret.ObjectMeta) } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go index 71a568ad4d..94ed01d7ff 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/target.go @@ -114,14 +114,14 @@ func (c RotatedSelfSignedCertKeySecret) EnsureTargetCertKeyPair(ctx context.Cont // run Update if metadata needs changing unless we're in RefreshOnlyWhenExpired mode if !c.RefreshOnlyWhenExpired { - needsMetadataUpdate := ensureMetadataUpdate(targetCertKeyPairSecret, c.Owner, c.AdditionalAnnotations) + needsMetadataUpdate := ensureOwnerRefAndTLSAnnotations(targetCertKeyPairSecret, c.Owner, c.AdditionalAnnotations) needsTypeChange := ensureSecretTLSTypeSet(targetCertKeyPairSecret) updateRequired = needsMetadataUpdate || needsTypeChange } if reason := c.CertCreator.NeedNewTargetCertKeyPair(targetCertKeyPairSecret, signingCertKeyPair, caBundleCerts, c.Refresh, c.RefreshOnlyWhenExpired, creationRequired); len(reason) > 0 { c.EventRecorder.Eventf("TargetUpdateRequired", "%q in %q requires a new target cert/key pair: %v", c.Name, c.Namespace, reason) - if err := setTargetCertKeyPairSecret(targetCertKeyPairSecret, c.Validity, signingCertKeyPair, c.CertCreator, c.AdditionalAnnotations); err != nil { + if err = setTargetCertKeyPairSecretAndTLSAnnotations(targetCertKeyPairSecret, c.Validity, c.Refresh, signingCertKeyPair, c.CertCreator, c.AdditionalAnnotations); err != nil { return nil, err } @@ -232,9 +232,21 @@ func needNewTargetCertKeyPairForTime(annotations map[string]string, signer *cryp return "" } +// setTargetCertKeyPairSecretAndTLSAnnotations generates a new cert/key pair, +// stores them in the specified secret, and adds predefined TLS annotations to that secret. +func setTargetCertKeyPairSecretAndTLSAnnotations(targetCertKeyPairSecret *corev1.Secret, validity, refresh time.Duration, signer *crypto.CA, certCreator TargetCertCreator, tlsAnnotations AdditionalAnnotations) error { + certKeyPair, err := setTargetCertKeyPairSecret(targetCertKeyPairSecret, validity, signer, certCreator) + if err != nil { + return err + } + + setTLSAnnotationsOnTargetCertKeyPairSecret(targetCertKeyPairSecret, certKeyPair, certCreator, refresh, tlsAnnotations) + return nil +} + // setTargetCertKeyPairSecret creates a new cert/key pair and sets them in the secret. Only one of client, serving, or signer rotation may be specified. // TODO refactor with an interface for actually signing and move the one-of check higher in the stack. -func setTargetCertKeyPairSecret(targetCertKeyPairSecret *corev1.Secret, validity time.Duration, signer *crypto.CA, certCreator TargetCertCreator, annotations AdditionalAnnotations) error { +func setTargetCertKeyPairSecret(targetCertKeyPairSecret *corev1.Secret, validity time.Duration, signer *crypto.CA, certCreator TargetCertCreator) (*crypto.TLSCertificateConfig, error) { if targetCertKeyPairSecret.Annotations == nil { targetCertKeyPairSecret.Annotations = map[string]string{} } @@ -251,21 +263,29 @@ func setTargetCertKeyPairSecret(targetCertKeyPairSecret *corev1.Secret, validity certKeyPair, err := certCreator.NewCertificate(signer, targetValidity) if err != nil { - return err + return nil, err } targetCertKeyPairSecret.Data["tls.crt"], targetCertKeyPairSecret.Data["tls.key"], err = certKeyPair.GetPEMBytes() - if err != nil { - return err - } - annotations.NotBefore = certKeyPair.Certs[0].NotBefore.Format(time.RFC3339) - annotations.NotAfter = certKeyPair.Certs[0].NotAfter.Format(time.RFC3339) + return certKeyPair, err +} + +// setTLSAnnotationsOnTargetCertKeyPairSecret applies predefined TLS annotations to the given secret. +// +// This function does not perform nil checks on its parameters and assumes that the +// secret's Annotations field has already been initialized. +// +// These assumptions are safe because this function is only called after the secret +// has been initialized in setTargetCertKeyPairSecret. +func setTLSAnnotationsOnTargetCertKeyPairSecret(targetCertKeyPairSecret *corev1.Secret, certKeyPair *crypto.TLSCertificateConfig, certCreator TargetCertCreator, refresh time.Duration, tlsAnnotations AdditionalAnnotations) { targetCertKeyPairSecret.Annotations[CertificateIssuer] = certKeyPair.Certs[0].Issuer.CommonName - _ = annotations.EnsureTLSMetadataUpdate(&targetCertKeyPairSecret.ObjectMeta) - certCreator.SetAnnotations(certKeyPair, targetCertKeyPairSecret.Annotations) + tlsAnnotations.NotBefore = certKeyPair.Certs[0].NotBefore.Format(time.RFC3339) + tlsAnnotations.NotAfter = certKeyPair.Certs[0].NotAfter.Format(time.RFC3339) + tlsAnnotations.RefreshPeriod = refresh.String() + _ = tlsAnnotations.EnsureTLSMetadataUpdate(&targetCertKeyPairSecret.ObjectMeta) - return nil + certCreator.SetAnnotations(certKeyPair, targetCertKeyPairSecret.Annotations) } type ClientRotation struct { diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resourcesynccontroller/core.go b/vendor/github.com/openshift/library-go/pkg/operator/resourcesynccontroller/core.go index 9711348b33..bbd2ab58fb 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/resourcesynccontroller/core.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/resourcesynccontroller/core.go @@ -70,3 +70,64 @@ func CombineCABundleConfigMaps(destinationConfigMap ResourceLocation, lister cor } return cm, nil } + +func CombineCABundleConfigMapsOptimistically(destinationConfigMap *corev1.ConfigMap, lister corev1listers.ConfigMapLister, additionalAnnotations certrotation.AdditionalAnnotations, inputConfigMaps ...ResourceLocation) (*corev1.ConfigMap, bool, error) { + var cm *corev1.ConfigMap + if destinationConfigMap == nil { + cm = &corev1.ConfigMap{} + } else { + cm = destinationConfigMap.DeepCopy() + } + certificates := []*x509.Certificate{} + for _, input := range inputConfigMaps { + inputConfigMap, err := lister.ConfigMaps(input.Namespace).Get(input.Name) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return nil, false, err + } + + // configmaps must conform to this + inputContent := inputConfigMap.Data["ca-bundle.crt"] + if len(inputContent) == 0 { + continue + } + inputCerts, err := cert.ParseCertsPEM([]byte(inputContent)) + if err != nil { + return nil, false, fmt.Errorf("configmap/%s in %q is malformed: %v", input.Name, input.Namespace, err) + } + certificates = append(certificates, inputCerts...) + } + + certificates = crypto.FilterExpiredCerts(certificates...) + finalCertificates := []*x509.Certificate{} + // now check for duplicates. n^2, but super simple + for i := range certificates { + found := false + for j := range finalCertificates { + if reflect.DeepEqual(certificates[i].Raw, finalCertificates[j].Raw) { + found = true + break + } + } + if !found { + finalCertificates = append(finalCertificates, certificates[i]) + } + } + + caBytes, err := crypto.EncodeCertificates(finalCertificates...) + if err != nil { + return nil, false, err + } + + modified := additionalAnnotations.EnsureTLSMetadataUpdate(&cm.ObjectMeta) + newCMData := map[string]string{ + "ca-bundle.crt": string(caBytes), + } + if !reflect.DeepEqual(cm.Data, newCMData) { + cm.Data = newCMData + modified = true + } + return cm, modified, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 678785fcfb..98e85f1481 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -346,7 +346,7 @@ github.com/openshift/client-go/security/informers/externalversions/internalinter github.com/openshift/client-go/security/informers/externalversions/security github.com/openshift/client-go/security/informers/externalversions/security/v1 github.com/openshift/client-go/security/listers/security/v1 -# github.com/openshift/library-go v0.0.0-20250710130336-73c7662bc565 +# github.com/openshift/library-go v0.0.0-20250724123005-03d85c4e997c ## explicit; go 1.24.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/assets