diff --git a/api/v1alpha1/database_types.go b/api/v1alpha1/database_types.go index 21e948e9..9bbe19b9 100644 --- a/api/v1alpha1/database_types.go +++ b/api/v1alpha1/database_types.go @@ -268,5 +268,6 @@ func init() { func (r *Database) AnyCertificatesAdded() bool { return len(r.Spec.CABundle) > 0 || r.Spec.Service.GRPC.TLSConfiguration.Enabled || - r.Spec.Service.Interconnect.TLSConfiguration.Enabled + r.Spec.Service.Interconnect.TLSConfiguration.Enabled || + r.Spec.Service.Status.TLSConfiguration.Enabled } diff --git a/api/v1alpha1/database_webhook.go b/api/v1alpha1/database_webhook.go index 461ec368..424a4490 100644 --- a/api/v1alpha1/database_webhook.go +++ b/api/v1alpha1/database_webhook.go @@ -106,6 +106,10 @@ func (r *DatabaseDefaulter) Default(ctx context.Context, obj runtime.Object) err database.Spec.Service.Datastreams.TLSConfiguration = &TLSConfiguration{Enabled: false} } + if database.Spec.Service.Status.TLSConfiguration == nil { + database.Spec.Service.Status.TLSConfiguration = &TLSConfiguration{Enabled: false} + } + if database.Spec.Domain == "" { database.Spec.Domain = DefaultDatabaseDomain } diff --git a/api/v1alpha1/service_types.go b/api/v1alpha1/service_types.go index e7c0aca9..7eb2b35f 100644 --- a/api/v1alpha1/service_types.go +++ b/api/v1alpha1/service_types.go @@ -32,6 +32,8 @@ type InterconnectService struct { type StatusService struct { Service `json:""` + + TLSConfiguration *TLSConfiguration `json:"tls,omitempty"` } type DatastreamsService struct { diff --git a/api/v1alpha1/storage_types.go b/api/v1alpha1/storage_types.go index c8e1d358..039b3a33 100644 --- a/api/v1alpha1/storage_types.go +++ b/api/v1alpha1/storage_types.go @@ -242,5 +242,6 @@ func init() { func (r *Storage) AnyCertificatesAdded() bool { return len(r.Spec.CABundle) > 0 || r.Spec.Service.GRPC.TLSConfiguration.Enabled || - r.Spec.Service.Interconnect.TLSConfiguration.Enabled + r.Spec.Service.Interconnect.TLSConfiguration.Enabled || + r.Spec.Service.Status.TLSConfiguration.Enabled } diff --git a/api/v1alpha1/storage_webhook.go b/api/v1alpha1/storage_webhook.go index c6135f65..a6f1f28f 100644 --- a/api/v1alpha1/storage_webhook.go +++ b/api/v1alpha1/storage_webhook.go @@ -156,6 +156,10 @@ func (r *StorageDefaulter) Default(ctx context.Context, obj runtime.Object) erro storage.Spec.Service.Interconnect.TLSConfiguration = &TLSConfiguration{Enabled: false} } + if storage.Spec.Service.Status.TLSConfiguration == nil { + storage.Spec.Service.Status.TLSConfiguration = &TLSConfiguration{Enabled: false} + } + if storage.Spec.Monitoring == nil { storage.Spec.Monitoring = &MonitoringOptions{ Enabled: false, diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fb95cce9..3f687fa4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1071,6 +1071,11 @@ func (in *StaticCredentialsAuth) DeepCopy() *StaticCredentialsAuth { func (in *StatusService) DeepCopyInto(out *StatusService) { *out = *in in.Service.DeepCopyInto(&out.Service) + if in.TLSConfiguration != nil { + in, out := &in.TLSConfiguration, &out.TLSConfiguration + *out = new(TLSConfiguration) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusService. diff --git a/deploy/ydb-operator/Chart.yaml b/deploy/ydb-operator/Chart.yaml index d5a1b3ed..166eee86 100644 --- a/deploy/ydb-operator/Chart.yaml +++ b/deploy/ydb-operator/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.5.13 +version: 0.5.14 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.5.13" +appVersion: "0.5.14" diff --git a/deploy/ydb-operator/crds/database.yaml b/deploy/ydb-operator/crds/database.yaml index c07efdad..e0b1976e 100644 --- a/deploy/ydb-operator/crds/database.yaml +++ b/deploy/ydb-operator/crds/database.yaml @@ -3994,6 +3994,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object sharedResources: diff --git a/deploy/ydb-operator/crds/databasenodeset.yaml b/deploy/ydb-operator/crds/databasenodeset.yaml index 2fd02003..bbbc2cf7 100644 --- a/deploy/ydb-operator/crds/databasenodeset.yaml +++ b/deploy/ydb-operator/crds/databasenodeset.yaml @@ -2699,6 +2699,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object sharedResources: diff --git a/deploy/ydb-operator/crds/remotedatabasenodeset.yaml b/deploy/ydb-operator/crds/remotedatabasenodeset.yaml index 8c193a63..706fa1ef 100644 --- a/deploy/ydb-operator/crds/remotedatabasenodeset.yaml +++ b/deploy/ydb-operator/crds/remotedatabasenodeset.yaml @@ -2700,6 +2700,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object sharedResources: diff --git a/deploy/ydb-operator/crds/remotestoragenodeset.yaml b/deploy/ydb-operator/crds/remotestoragenodeset.yaml index 00577e09..e8053cc3 100644 --- a/deploy/ydb-operator/crds/remotestoragenodeset.yaml +++ b/deploy/ydb-operator/crds/remotestoragenodeset.yaml @@ -2710,6 +2710,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object storageRef: diff --git a/deploy/ydb-operator/crds/storage.yaml b/deploy/ydb-operator/crds/storage.yaml index 7cde1a93..650c39c1 100644 --- a/deploy/ydb-operator/crds/storage.yaml +++ b/deploy/ydb-operator/crds/storage.yaml @@ -5166,6 +5166,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object terminationGracePeriodSeconds: diff --git a/deploy/ydb-operator/crds/storagenodeset.yaml b/deploy/ydb-operator/crds/storagenodeset.yaml index c2365985..0c797170 100644 --- a/deploy/ydb-operator/crds/storagenodeset.yaml +++ b/deploy/ydb-operator/crds/storagenodeset.yaml @@ -2709,6 +2709,70 @@ spec: description: IPFamilyPolicy represents the dual-stack-ness requested or required by a Service type: string + tls: + properties: + CA: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + certificate: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + enabled: + type: boolean + key: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + required: + - enabled + type: object type: object type: object storageRef: diff --git a/e2e/tests/smoke_test.go b/e2e/tests/smoke_test.go index 9b500958..2dd9bfce 100644 --- a/e2e/tests/smoke_test.go +++ b/e2e/tests/smoke_test.go @@ -1,8 +1,15 @@ package tests import ( + "bufio" "context" + "crypto/tls" + "crypto/x509" + "errors" "fmt" + "io" + "net" + "net/http" "os" "os/exec" "path/filepath" @@ -23,6 +30,7 @@ import ( v1alpha1 "github.com/ydb-platform/ydb-kubernetes-operator/api/v1alpha1" testobjects "github.com/ydb-platform/ydb-kubernetes-operator/e2e/tests/test-objects" . "github.com/ydb-platform/ydb-kubernetes-operator/internal/controllers/constants" + "github.com/ydb-platform/ydb-kubernetes-operator/internal/resources" ) const ( @@ -179,6 +187,68 @@ func executeSimpleQuery(ctx context.Context, podName, podNamespace, storageEndpo }, Timeout, Interval).Should(MatchRegexp(".*column0.*1.*")) } +func portForward(ctx context.Context, svcName string, svcNamespace string, port int, f func(int) error) { + Eventually(func(g Gomega) error { + args := []string{ + "-n", svcNamespace, + "port-forward", + fmt.Sprintf("svc/%s", svcName), + fmt.Sprintf(":%d", port), + } + + cmd := exec.CommandContext(ctx, "kubectl", args...) + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + + if err = cmd.Start(); err != nil { + return err + } + + defer func() { + err := cmd.Process.Kill() + if err != nil { + _, _ = fmt.Fprintf(GinkgoWriter, "Unable to kill process: %s", err) + } + }() + + localPort := 0 + + scanner := bufio.NewScanner(stdout) + portForwardRegex := regexp.MustCompile(`Forwarding from 127.0.0.1:(\d+) ->`) + + for scanner.Scan() { + line := scanner.Text() + + matches := portForwardRegex.FindStringSubmatch(line) + if matches != nil { + localPort, err = strconv.Atoi(matches[1]) + if err != nil { + return err + } + break + } + } + + if localPort != 0 { + if err = f(localPort); err != nil { + return err + } + } else { + content, _ := io.ReadAll(stderr) + + return fmt.Errorf("kubectl port-forward stderr: %s", content) + } + return nil + }, 60*time.Second, Interval).Should(BeNil()) +} + var _ = Describe("Operator smoke test", func() { var ctx context.Context var namespace corev1.Namespace @@ -602,6 +672,103 @@ var _ = Describe("Operator smoke test", func() { checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-database", databaseSample.Spec.Nodes) }) + It("TLS for status service", func() { + tlsHTTPCheck := func(port int) error { + url := fmt.Sprintf("https://localhost:%d/", port) + cert, err := os.ReadFile(filepath.Join(".", "data", "ca.crt")) + Expect(err).ShouldNot(HaveOccurred()) + + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM(cert) + Expect(ok).To(BeTrue()) + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: certPool, + ServerName: "storage-grpc.ydb.svc.cluster.local", + } + + transport := &http.Transport{TLSClientConfig: tlsConfig} + client := &http.Client{ + Transport: transport, + Timeout: 10 * time.Second, + } + resp, err := client.Get(url) + if err != nil { + var netError net.Error + var opError *net.OpError + + // for database: operator sets database ready status before the database is actually can server requests. + if errors.As(err, &netError) && netError.Timeout() || errors.As(err, &opError) || errors.Is(err, io.EOF) { + return err + } + + Expect(err).ShouldNot(HaveOccurred()) + + } + + Expect(resp).To(HaveHTTPStatus(http.StatusOK)) + + return nil + } + + By("create secret...") + cert := testobjects.DefaultCertificate( + filepath.Join(".", "data", "tls.crt"), + filepath.Join(".", "data", "tls.key"), + filepath.Join(".", "data", "ca.crt"), + ) + Expect(k8sClient.Create(ctx, cert)).Should(Succeed()) + + By("create storage...") + storageSample.Spec.Service.Status.TLSConfiguration = &v1alpha1.TLSConfiguration{ + Enabled: true, + Certificate: corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName}, + Key: "tls.crt", + }, + Key: corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName}, + Key: "tls.key", + }, + CertificateAuthority: corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: testobjects.CertificateSecretName}, + Key: "ca.crt", + }, + } + + Expect(k8sClient.Create(ctx, storageSample)).Should(Succeed()) + + By("create database...") + databaseSample.Spec.Nodes = 1 + databaseSample.Spec.Service.Status = *storageSample.Spec.Service.Status.DeepCopy() + Expect(k8sClient.Create(ctx, databaseSample)).Should(Succeed()) + + By("waiting until Storage is ready...") + waitUntilStorageReady(ctx, storageSample.Name, testobjects.YdbNamespace) + + By("checking that all the storage pods are running and ready...") + checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-storage", storageSample.Spec.Nodes) + + By("forward storage status port and check that we can check TLS response") + portForward(ctx, + fmt.Sprintf(resources.StatusServiceNameFormat, storageSample.Name), storageSample.Namespace, + v1alpha1.StatusPort, tlsHTTPCheck, + ) + + By("waiting until database is ready...") + waitUntilDatabaseReady(ctx, databaseSample.Name, testobjects.YdbNamespace) + + By("checking that all the database pods are running and ready...") + checkPodsRunningAndReady(ctx, "ydb-cluster", "kind-database", databaseSample.Spec.Nodes) + + By("forward database status port and check that we can check TLS response") + portForward(ctx, + fmt.Sprintf(resources.StatusServiceNameFormat, databaseSample.Name), databaseSample.Namespace, + v1alpha1.StatusPort, tlsHTTPCheck, + ) + }) + AfterEach(func() { Expect(uninstallOperatorWithHelm(testobjects.YdbNamespace)).Should(BeTrue()) Expect(k8sClient.Delete(ctx, &namespace)).Should(Succeed()) diff --git a/e2e/tests/test-objects/objects.go b/e2e/tests/test-objects/objects.go index d2b1e75e..d9fc5648 100644 --- a/e2e/tests/test-objects/objects.go +++ b/e2e/tests/test-objects/objects.go @@ -78,6 +78,9 @@ func DefaultStorage(storageYamlConfigPath string) *v1alpha1.Storage { }, Status: v1alpha1.StatusService{ Service: v1alpha1.Service{IPFamilies: []corev1.IPFamily{"IPv4"}}, + TLSConfiguration: &v1alpha1.TLSConfiguration{ + Enabled: false, + }, }, }, Monitoring: &v1alpha1.MonitoringOptions{ @@ -138,6 +141,9 @@ func DefaultDatabase() *v1alpha1.Database { }, Status: v1alpha1.StatusService{ Service: v1alpha1.Service{IPFamilies: []corev1.IPFamily{"IPv4"}}, + TLSConfiguration: &v1alpha1.TLSConfiguration{ + Enabled: false, + }, }, }, Datastreams: &v1alpha1.DatastreamsConfig{ diff --git a/internal/resources/database.go b/internal/resources/database.go index f1f3c06f..0214c253 100644 --- a/internal/resources/database.go +++ b/internal/resources/database.go @@ -17,6 +17,10 @@ type DatabaseBuilder struct { func NewDatabase(ydbCr *api.Database) DatabaseBuilder { cr := ydbCr.DeepCopy() + if cr.Spec.Service.Status.TLSConfiguration == nil { + cr.Spec.Service.Status.TLSConfiguration = &api.TLSConfiguration{Enabled: false} + } + return DatabaseBuilder{Database: cr, Storage: nil} } diff --git a/internal/resources/database_statefulset.go b/internal/resources/database_statefulset.go index 1ad985a6..c9437b50 100644 --- a/internal/resources/database_statefulset.go +++ b/internal/resources/database_statefulset.go @@ -168,6 +168,18 @@ func (b *DatabaseStatefulSetBuilder) buildVolumes() []corev1.Volume { volumes = append(volumes, buildTLSVolume(interconnectTLSVolumeName, b.Spec.Service.Interconnect.TLSConfiguration)) } + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumes = append(volumes, + buildTLSVolume(statusOriginTLSVolumeName, b.Spec.Service.Status.TLSConfiguration), + corev1.Volume{ + Name: statusTLSVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + ) + } + if b.Spec.Encryption != nil && b.Spec.Encryption.Enabled { volumes = append(volumes, b.buildEncryptionVolume()) } @@ -218,6 +230,7 @@ func (b *DatabaseStatefulSetBuilder) buildCaStorePatchingInitContainer() corev1. b.Spec.CABundle, b.Spec.Service.GRPC, b.Spec.Service.Interconnect, + b.Spec.Service.Status, ) imagePullPolicy := corev1.PullIfNotPresent if b.Spec.Image.PullPolicyName != nil { @@ -292,6 +305,19 @@ func (b *DatabaseStatefulSetBuilder) buildCaStorePatchingInitContainerVolumeMoun MountPath: datastreamsTLSVolumeMountPath, }) } + + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusOriginTLSVolumeName, + ReadOnly: true, + MountPath: statusOriginTLSVolumeMountPath, + }) + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusTLSVolumeName, + MountPath: statusTLSVolumeMountPath, + }) + } + return volumeMounts } @@ -448,6 +474,14 @@ func (b *DatabaseStatefulSetBuilder) buildVolumeMounts() []corev1.VolumeMount { }) } + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusTLSVolumeName, + ReadOnly: true, + MountPath: statusTLSVolumeMountPath, + }) + } + if b.Spec.Encryption != nil && b.Spec.Encryption.Enabled { volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: encryptionVolumeName, @@ -549,6 +583,13 @@ func (b *DatabaseStatefulSetBuilder) buildContainerArgs() ([]string, []string) { ) } + if b.Spec.Service.Status.TLSConfiguration.Enabled { + args = append(args, + "--mon-cert", + fmt.Sprintf("%s/%s", statusTLSVolumeMountPath, statusBundleFileName), + ) + } + for _, secret := range b.Spec.Secrets { exist, err := CheckSecretKey( context.Background(), diff --git a/internal/resources/resource.go b/internal/resources/resource.go index a9d60517..f9dec308 100644 --- a/internal/resources/resource.go +++ b/internal/resources/resource.go @@ -40,10 +40,14 @@ const ( grpcTLSVolumeName = "grpc-tls-volume" interconnectTLSVolumeName = "interconnect-tls-volume" datastreamsTLSVolumeName = "datastreams-tls-volume" + statusTLSVolumeName = "status-tls-volume" + statusOriginTLSVolumeName = "status-origin-tls-volume" grpcTLSVolumeMountPath = "/tls/grpc" interconnectTLSVolumeMountPath = "/tls/interconnect" datastreamsTLSVolumeMountPath = "/tls/datastreams" + statusTLSVolumeMountPath = "/tls/status" + statusOriginTLSVolumeMountPath = "/tls/status-origin" InitJobNameFormat = "%s-blobstorage-init" OperatorTokenSecretNameFormat = "%s-operator-token" @@ -63,6 +67,7 @@ const ( caBundleFileName = "userCABundle.crt" caCertificatesFileName = "ca-certificates.crt" updateCACertificatesBin = "update-ca-certificates" + statusBundleFileName = "web.pem" localCertsDir = "/usr/local/share/ca-certificates" systemCertsDir = "/etc/ssl/certs" @@ -503,6 +508,7 @@ func buildCAStorePatchingCommandArgs( caBundle string, grpcService api.GRPCService, interconnectService api.InterconnectService, + statusService api.StatusService, ) ([]string, []string) { command := []string{"/bin/bash", "-c"} @@ -520,6 +526,16 @@ func buildCAStorePatchingCommandArgs( arg += fmt.Sprintf("cp %s/%s %s/interconnectRoot.crt && ", interconnectTLSVolumeMountPath, wellKnownNameForTLSCertificateAuthority, localCertsDir) } + if statusService.TLSConfiguration != nil && statusService.TLSConfiguration.Enabled { + arg += fmt.Sprintf("cp %s/%s %s/web.crt && ", statusOriginTLSVolumeMountPath, wellKnownNameForTLSCertificateAuthority, localCertsDir) + arg += fmt.Sprintf("cat %s/%s %s/%s %s/%s > %s/%s && ", + statusOriginTLSVolumeMountPath, wellKnownNameForTLSPrivateKey, + statusOriginTLSVolumeMountPath, wellKnownNameForTLSCertificate, + statusOriginTLSVolumeMountPath, wellKnownNameForTLSCertificateAuthority, + statusTLSVolumeMountPath, statusBundleFileName, + ) + } + if arg != "" { arg += updateCACertificatesBin } diff --git a/internal/resources/storage.go b/internal/resources/storage.go index ced81300..d8f69751 100644 --- a/internal/resources/storage.go +++ b/internal/resources/storage.go @@ -16,6 +16,10 @@ type StorageClusterBuilder struct { func NewCluster(ydbCr *api.Storage) StorageClusterBuilder { cr := ydbCr.DeepCopy() + if cr.Spec.Service.Status.TLSConfiguration == nil { + cr.Spec.Service.Status.TLSConfiguration = &api.TLSConfiguration{Enabled: false} + } + return StorageClusterBuilder{cr} } diff --git a/internal/resources/storage_init_job.go b/internal/resources/storage_init_job.go index 88d588e6..0507623e 100644 --- a/internal/resources/storage_init_job.go +++ b/internal/resources/storage_init_job.go @@ -278,6 +278,7 @@ func (b *StorageInitJobBuilder) buildCaStorePatchingInitContainer() corev1.Conta b.Spec.CABundle, b.Spec.Service.GRPC, b.Spec.Service.Interconnect, + api.StatusService{TLSConfiguration: &api.TLSConfiguration{Enabled: false}}, ) imagePullPolicy := corev1.PullIfNotPresent if b.Spec.Image.PullPolicyName != nil { diff --git a/internal/resources/storage_statefulset.go b/internal/resources/storage_statefulset.go index 61fd1e5d..7a92f2cc 100644 --- a/internal/resources/storage_statefulset.go +++ b/internal/resources/storage_statefulset.go @@ -212,6 +212,18 @@ func (b *StorageStatefulSetBuilder) buildVolumes() []corev1.Volume { volumes = append(volumes, buildTLSVolume(interconnectTLSVolumeName, b.Spec.Service.Interconnect.TLSConfiguration)) } + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumes = append(volumes, + buildTLSVolume(statusOriginTLSVolumeName, b.Spec.Service.Status.TLSConfiguration), + corev1.Volume{ + Name: statusTLSVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + ) + } + for _, secret := range b.Spec.Secrets { volumes = append(volumes, corev1.Volume{ Name: secret.Name, @@ -251,6 +263,7 @@ func (b *StorageStatefulSetBuilder) buildCaStorePatchingInitContainer() corev1.C b.Spec.CABundle, b.Spec.Service.GRPC, b.Spec.Service.Interconnect, + b.Spec.Service.Status, ) containerResources := corev1.ResourceRequirements{} if b.Spec.Resources != nil { @@ -315,6 +328,20 @@ func (b *StorageStatefulSetBuilder) buildCaStorePatchingInitContainerVolumeMount MountPath: interconnectTLSVolumeMountPath, }) } + + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusOriginTLSVolumeName, + ReadOnly: true, + MountPath: statusOriginTLSVolumeMountPath, + }) + + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusTLSVolumeName, + MountPath: statusTLSVolumeMountPath, + }) + } + return volumeMounts } @@ -419,6 +446,14 @@ func (b *StorageStatefulSetBuilder) buildVolumeMounts() []corev1.VolumeMount { }) } + if b.Spec.Service.Status.TLSConfiguration.Enabled { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: statusTLSVolumeName, + ReadOnly: true, + MountPath: statusTLSVolumeMountPath, + }) + } + if b.AnyCertificatesAdded() { volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: localCertsVolumeName, @@ -471,6 +506,13 @@ func (b *StorageStatefulSetBuilder) buildContainerArgs() ([]string, []string) { fmt.Sprintf("%s=%s", api.LabelDeploymentKey, api.LabelDeploymentValueKubernetes), ) + if b.Spec.Service.Status.TLSConfiguration.Enabled { + args = append(args, + "--mon-cert", + fmt.Sprintf("%s/%s", statusTLSVolumeMountPath, statusBundleFileName), + ) + } + for _, secret := range b.Spec.Secrets { exist, err := CheckSecretKey( context.Background(),