From abc38679aaaff774e7a79ad097b47f98b2b5044e Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Mon, 19 Aug 2024 14:23:46 +0200 Subject: [PATCH] Append user-specified additional hostnames to certificates generated by CA issuer --- api/v1alpha1/humiocluster_types.go | 2 + api/v1alpha1/zz_generated.deepcopy.go | 5 +++ .../crds/core.humio.com_humioclusters.yaml | 6 +++ .../bases/core.humio.com_humioclusters.yaml | 6 +++ controllers/humiocluster_controller.go | 18 +++++---- controllers/humiocluster_defaults.go | 4 ++ controllers/humiocluster_tls.go | 15 ++++++-- .../clusters/humiocluster_controller_test.go | 38 +++++++++++++++++++ 8 files changed, 82 insertions(+), 12 deletions(-) diff --git a/api/v1alpha1/humiocluster_types.go b/api/v1alpha1/humiocluster_types.go index e5d236e1..aa87b743 100644 --- a/api/v1alpha1/humiocluster_types.go +++ b/api/v1alpha1/humiocluster_types.go @@ -327,6 +327,8 @@ type HumioClusterTLSSpec struct { Enabled *bool `json:"enabled,omitempty"` // CASecretName is used to point to a Kubernetes secret that holds the CA that will be used to issue intra-cluster TLS certificates CASecretName string `json:"caSecretName,omitempty"` + // ExtraHostnames holds a list of additional hostnames that will be appended to TLS certificates. + ExtraHostnames []string `json:"extraHostnames,omitempty"` } // HumioClusterLicenseSpec points to the optional location of the Humio license diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 094ce051..ba56c5f6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -727,6 +727,11 @@ func (in *HumioClusterTLSSpec) DeepCopyInto(out *HumioClusterTLSSpec) { *out = new(bool) **out = **in } + if in.ExtraHostnames != nil { + in, out := &in.ExtraHostnames, &out.ExtraHostnames + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioClusterTLSSpec. diff --git a/charts/humio-operator/crds/core.humio.com_humioclusters.yaml b/charts/humio-operator/crds/core.humio.com_humioclusters.yaml index f1ddfa01..ab43f7ec 100644 --- a/charts/humio-operator/crds/core.humio.com_humioclusters.yaml +++ b/charts/humio-operator/crds/core.humio.com_humioclusters.yaml @@ -14754,6 +14754,12 @@ spec: behaviour is to configure TLS if cert-manager is present, otherwise we skip TLS. type: boolean + extraHostnames: + description: ExtraHostnames holds a list of additional hostnames + that will be appended to TLS certificates. + items: + type: string + type: array type: object tolerations: description: Tolerations defines the tolerations that will be attached diff --git a/config/crd/bases/core.humio.com_humioclusters.yaml b/config/crd/bases/core.humio.com_humioclusters.yaml index f1ddfa01..ab43f7ec 100644 --- a/config/crd/bases/core.humio.com_humioclusters.yaml +++ b/config/crd/bases/core.humio.com_humioclusters.yaml @@ -14754,6 +14754,12 @@ spec: behaviour is to configure TLS if cert-manager is present, otherwise we skip TLS. type: boolean + extraHostnames: + description: ExtraHostnames holds a list of additional hostnames + that will be appended to TLS certificates. + items: + type: string + type: array type: object tolerations: description: Tolerations defines the tolerations that will be attached diff --git a/controllers/humiocluster_controller.go b/controllers/humiocluster_controller.go index 367939a9..732dc0ea 100644 --- a/controllers/humiocluster_controller.go +++ b/controllers/humiocluster_controller.go @@ -958,17 +958,19 @@ func (r *HumioClusterReconciler) ensureValidCASecret(ctx context.Context, hc *hu } r.Log.Info("checking for an existing CA secret") - validCASecret, err := validCASecret(ctx, r, hc.Namespace, getCASecretName(hc)) - if validCASecret { - r.Log.Info("found valid CA secret") + caSecretIsValid, err := validCASecret(ctx, r, hc.Namespace, getCASecretName(hc)) + if caSecretIsValid { + r.Log.Info("found valid CA secret, nothing more to do") return nil } - if err != nil && !k8serrors.IsNotFound(err) { - return r.logErrorAndReturn(err, "could not validate CA secret") - } - + // CA secret is not valid, return if user specified their own custom CA secret if useExistingCA(hc) { - return r.logErrorAndReturn(fmt.Errorf("configured to use existing CA secret, but the CA secret invalid"), "specified CA secret invalid") + return r.logErrorAndReturn(fmt.Errorf("configured to use existing CA secret, but the CA secret is invalid or got error when validating, err=%v", err), "specified CA secret invalid") + } + // CA secret is not valid, and should generate our own if it is not already present + if !k8serrors.IsNotFound(err) { + // Got error that was not due to the k8s secret not existing + return r.logErrorAndReturn(err, "could not validate CA secret") } r.Log.Info("generating new CA certificate") diff --git a/controllers/humiocluster_defaults.go b/controllers/humiocluster_defaults.go index a9c768bc..3097248d 100644 --- a/controllers/humiocluster_defaults.go +++ b/controllers/humiocluster_defaults.go @@ -815,6 +815,10 @@ func (hnp *HumioNodePool) TLSEnabled() bool { return helpers.UseCertManager() && *hnp.tls.Enabled } +func (hnp *HumioNodePool) GetTLSSpec() *humiov1alpha1.HumioClusterTLSSpec { + return hnp.tls +} + func (hnp *HumioNodePool) GetProbeScheme() corev1.URIScheme { if !hnp.TLSEnabled() { return corev1.URISchemeHTTP diff --git a/controllers/humiocluster_tls.go b/controllers/humiocluster_tls.go index a1fe1c19..fcb6a673 100644 --- a/controllers/humiocluster_tls.go +++ b/controllers/humiocluster_tls.go @@ -64,7 +64,7 @@ func validCASecret(ctx context.Context, k8sclient client.Client, namespace, secr // look up k8s secret secret, err := kubernetes.GetSecret(ctx, k8sclient, secretName, namespace) if err != nil { - return false, nil + return false, err } keys := []string{"tls.crt", "tls.key"} for _, key := range keys { @@ -165,7 +165,7 @@ func constructCAIssuer(hc *humiov1alpha1.HumioCluster) cmapi.Issuer { } func constructClusterCACertificateBundle(hc *humiov1alpha1.HumioCluster) cmapi.Certificate { - return cmapi.Certificate{ + certificate := cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Namespace: hc.Namespace, Name: hc.Name, @@ -183,10 +183,14 @@ func constructClusterCACertificateBundle(hc *humiov1alpha1.HumioCluster) cmapi.C SecretName: hc.Name, }, } + if hc.Spec.TLS != nil { + certificate.Spec.DNSNames = append(certificate.Spec.DNSNames, hc.Spec.TLS.ExtraHostnames...) + } + return certificate } func ConstructNodeCertificate(hnp *HumioNodePool, nodeSuffix string) cmapi.Certificate { - return cmapi.Certificate{ + certificate := cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{}, Namespace: hnp.GetNamespace(), @@ -200,7 +204,6 @@ func ConstructNodeCertificate(hnp *HumioNodePool, nodeSuffix string) cmapi.Certi fmt.Sprintf("%s.%s", hnp.GetNodePoolName(), hnp.GetNamespace()), // Used by ingress controllers to reach the Humio API fmt.Sprintf("%s-headless.%s", hnp.GetClusterName(), hnp.GetNamespace()), // Used for intra-cluster communication fmt.Sprintf("%s-internal.%s", hnp.GetClusterName(), hnp.GetNamespace()), // Used by humio-operator to reach the Humio API - }, IssuerRef: cmmeta.ObjectReference{ Name: hnp.GetClusterName(), @@ -219,6 +222,10 @@ func ConstructNodeCertificate(hnp *HumioNodePool, nodeSuffix string) cmapi.Certi }, }, } + if hnp.GetTLSSpec() != nil { + certificate.Spec.DNSNames = append(certificate.Spec.DNSNames, hnp.GetTLSSpec().ExtraHostnames...) + } + return certificate } func GetDesiredCertHash(hnp *HumioNodePool) string { diff --git a/controllers/suite/clusters/humiocluster_controller_test.go b/controllers/suite/clusters/humiocluster_controller_test.go index 7a4384c1..5640addd 100644 --- a/controllers/suite/clusters/humiocluster_controller_test.go +++ b/controllers/suite/clusters/humiocluster_controller_test.go @@ -19,6 +19,7 @@ package clusters import ( "context" "fmt" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "os" "reflect" "strings" @@ -3790,6 +3791,43 @@ var _ = Describe("HumioCluster Controller", func() { }) }) + Context("Humio Cluster with additional hostnames for TLS", func() { + It("Creating cluster with additional hostnames for TLS", func() { + if os.Getenv("TEST_USE_EXISTING_CLUSTER") == "true" { + key := types.NamespacedName{ + Name: "humiocluster-tls-additional-hostnames", + Namespace: testProcessNamespace, + } + toCreate := suite.ConstructBasicSingleNodeHumioCluster(key, true) + toCreate.Spec.TLS = &humiov1alpha1.HumioClusterTLSSpec{ + Enabled: helpers.BoolPtr(true), + ExtraHostnames: []string{ + "something.additional", + "yet.another.something.additional", + }, + } + + suite.UsingClusterBy(key.Name, "Creating the cluster successfully") + ctx := context.Background() + suite.CreateAndBootstrapCluster(ctx, k8sClient, humioClientForTestSuite, toCreate, true, humiov1alpha1.HumioClusterStateRunning, testTimeout) + defer suite.CleanupCluster(ctx, k8sClient, toCreate) + + suite.UsingClusterBy(key.Name, "Confirming certificate objects contain the additional hostnames") + + Eventually(func() ([]cmapi.Certificate, error) { + return kubernetes.ListCertificates(ctx, k8sClient, toCreate.Namespace, kubernetes.MatchingLabelsForHumio(toCreate.Name)) + }, testTimeout, suite.TestInterval).Should(HaveLen(2)) + + var certificates []cmapi.Certificate + certificates, err = kubernetes.ListCertificates(ctx, k8sClient, toCreate.Namespace, kubernetes.MatchingLabelsForHumio(toCreate.Name)) + Expect(err).To(Succeed()) + for _, certificate := range certificates { + Expect(certificate.Spec.DNSNames).Should(ContainElements(toCreate.Spec.TLS.ExtraHostnames)) + } + } + }) + }) + Context("Humio Cluster Ingress", func() { It("Should correctly handle ingress when toggling both ESHostname and Hostname on/off", func() { key := types.NamespacedName{