diff --git a/controllers/controller.go b/controllers/controller.go index 39b8be7e..b2d2e675 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -244,7 +244,7 @@ func (r *RpaasInstanceReconciler) reconcileTLSSessionResumption(ctx context.Cont } func (r *RpaasInstanceReconciler) reconcileSecretForSessionTickets(ctx context.Context, instance *v1alpha1.RpaasInstance) (hasChanged bool, err error) { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) newSecret, err := newSecretForTLSSessionTickets(instance) if err != nil { @@ -297,7 +297,7 @@ func (r *RpaasInstanceReconciler) reconcileSecretForSessionTickets(ctx context.C } func (r *RpaasInstanceReconciler) reconcileCronJobForSessionTickets(ctx context.Context, instance *v1alpha1.RpaasInstance) (hasChanged bool, err error) { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) newCronJob := newCronJobForSessionTickets(instance) @@ -344,7 +344,7 @@ func (r *RpaasInstanceReconciler) reconcileCronJobForSessionTickets(ctx context. } func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.CronJob { - enabled := isTLSSessionTicketEnabled(instance) + enabled := isTLSSessionTicketEnabled(&instance.Spec) keyLength := v1alpha1.DefaultSessionTicketKeyLength if enabled && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { @@ -409,7 +409,7 @@ func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.Cron Env: []corev1.EnvVar{ { Name: "SECRET_NAME", - Value: secretNameForTLSSessionTickets(instance), + Value: secretNameForTLSSessionTickets(instance.Name), }, { Name: "SECRET_NAMESPACE", @@ -464,7 +464,7 @@ func newCronJobForSessionTickets(instance *v1alpha1.RpaasInstance) *batchv1.Cron func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Secret, error) { keyLength := v1alpha1.DefaultSessionTicketKeyLength - if isTLSSessionTicketEnabled(instance) && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { + if isTLSSessionTicketEnabled(&instance.Spec) && instance.Spec.TLSSessionResumption.SessionTicket.KeyLength != 0 { keyLength = instance.Spec.TLSSessionResumption.SessionTicket.KeyLength } @@ -484,7 +484,7 @@ func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Se Kind: "Secret", }, ObjectMeta: metav1.ObjectMeta{ - Name: secretNameForTLSSessionTickets(instance), + Name: secretNameForTLSSessionTickets(instance.Name), Namespace: instance.Namespace, Labels: instance.GetBaseLabels(nil), OwnerReferences: []metav1.OwnerReference{ @@ -499,20 +499,20 @@ func newSecretForTLSSessionTickets(instance *v1alpha1.RpaasInstance) (*corev1.Se }, nil } -func isTLSSessionTicketEnabled(instance *v1alpha1.RpaasInstance) bool { - return instance.Spec.TLSSessionResumption != nil && instance.Spec.TLSSessionResumption.SessionTicket != nil +func isTLSSessionTicketEnabled(spec *v1alpha1.RpaasInstanceSpec) bool { + return spec.TLSSessionResumption != nil && spec.TLSSessionResumption.SessionTicket != nil } func tlsSessionTicketKeys(instance *v1alpha1.RpaasInstance) int { var nkeys int - if isTLSSessionTicketEnabled(instance) { + if isTLSSessionTicketEnabled(&instance.Spec) { nkeys = int(instance.Spec.TLSSessionResumption.SessionTicket.KeepLastKeys) } return nkeys + 1 } -func secretNameForTLSSessionTickets(instance *v1alpha1.RpaasInstance) string { - return fmt.Sprintf("%s%s", instance.Name, sessionTicketsSecretSuffix) +func secretNameForTLSSessionTickets(instanceName string) string { + return fmt.Sprintf("%s%s", instanceName, sessionTicketsSecretSuffix) } func generateSessionTicket(keyLength v1alpha1.SessionTicketKeyLength) ([]byte, error) { @@ -1168,12 +1168,12 @@ func newNginx(instanceMergedWithFlavors *v1alpha1.RpaasInstance, plan *v1alpha1. }) } - if isTLSSessionTicketEnabled(instanceMergedWithFlavors) { + if isTLSSessionTicketEnabled(&instanceMergedWithFlavors.Spec) { n.Spec.PodTemplate.Volumes = append(n.Spec.PodTemplate.Volumes, corev1.Volume{ Name: sessionTicketsVolumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: secretNameForTLSSessionTickets(instanceMergedWithFlavors), + SecretName: secretNameForTLSSessionTickets(instanceMergedWithFlavors.Name), }, }, }) diff --git a/controllers/validation_controller.go b/controllers/validation_controller.go index 7eb682c1..0654c132 100644 --- a/controllers/validation_controller.go +++ b/controllers/validation_controller.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "path/filepath" "github.com/go-logr/logr" "github.com/tsuru/rpaas-operator/api/v1alpha1" @@ -14,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -406,6 +408,7 @@ func newValidationPod(validationMergedWithFlavors *v1alpha1.RpaasValidation, val VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: f.ConfigMap.LocalObjectReference, + Optional: pointer.Bool(false), }, }, }) @@ -457,6 +460,44 @@ func newValidationPod(validationMergedWithFlavors *v1alpha1.RpaasValidation, val }) } + for index, t := range validationMergedWithFlavors.Spec.TLS { + volumeName := fmt.Sprintf("nginx-certs-%d", index) + + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: t.SecretName, + Optional: ptr.To(false), + }, + }, + }) + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: filepath.Join(configMountPath, "certs", t.SecretName), + ReadOnly: true, + }) + } + + if isTLSSessionTicketEnabled(&validationMergedWithFlavors.Spec) { + n.Spec.Volumes = append(n.Spec.Volumes, corev1.Volume{ + Name: sessionTicketsVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretNameForTLSSessionTickets(validationMergedWithFlavors.Name), + Optional: ptr.To(false), + }, + }, + }) + + n.Spec.Containers[0].VolumeMounts = append(n.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: sessionTicketsVolumeName, + MountPath: sessionTicketsVolumeMountPath, + ReadOnly: true, + }) + } + return n } diff --git a/controllers/validation_controller_test.go b/controllers/validation_controller_test.go new file mode 100644 index 00000000..e482d18d --- /dev/null +++ b/controllers/validation_controller_test.go @@ -0,0 +1,253 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package controllers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" +) + +func TestNewValidationPod(t *testing.T) { + pod := newValidationPod(&v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + UID: types.UID("blah"), + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, "hash", + &v1alpha1.RpaasPlan{}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + }, + }, + ) + + assert.Equal(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + Annotations: map[string]string{ + "rpaas.extensions.tsuru.io/validation-hash": "hash", + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions.tsuru.io/v1alpha1", + Kind: "RpaasValidation", + Name: "valid", + UID: types.UID("blah"), + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }, + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "valid-config", + }, + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, + }, pod) +} + +func TestNewValidationPodFullFeatured(t *testing.T) { + pod := newValidationPod( + &v1alpha1.RpaasValidation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid", + UID: types.UID("blah"), + }, + Spec: v1alpha1.RpaasInstanceSpec{ + Files: []v1alpha1.File{ + { + Name: "myfile", + ConfigMap: &corev1.ConfigMapKeySelector{ + Key: "myfile", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myfile", + }, + }, + }, + }, + TLS: []nginxv1alpha1.NginxTLS{ + { + SecretName: "secret-tls", + Hosts: []string{ + "host1", + "host2", + }, + }, + }, + + TLSSessionResumption: &v1alpha1.TLSSessionResumption{ + SessionTicket: &v1alpha1.TLSSessionTicket{ + KeepLastKeys: 2, + }, + }, + }, + }, + "hash", + &v1alpha1.RpaasPlan{ + Spec: v1alpha1.RpaasPlanSpec{ + Config: v1alpha1.NginxConfig{ + CacheEnabled: ptr.To(true), + CachePath: "/var/cache", + }, + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + }, + }, + ) + + assert.Equal(t, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-config", + Annotations: map[string]string{ + "rpaas.extensions.tsuru.io/validation-hash": "hash", + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "extensions.tsuru.io/v1alpha1", + Kind: "RpaasValidation", + Name: "valid", + UID: types.UID("blah"), + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + }, + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "extra-files-0", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "myfile", + }, + Optional: ptr.To(false), + }, + }, + }, + { + Name: "nginx-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "valid-config", + }, + Optional: ptr.To(false), + }, + }, + }, + { + Name: "cache-vol", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + }, + { + Name: "nginx-certs-0", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "secret-tls", + Optional: ptr.To(false), + }, + }, + }, + { + Name: "tls-session-tickets", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "valid-session-tickets", + Optional: ptr.To(false), + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "validation", + Command: []string{ + "/bin/sh", + "-c", + "nginx -t 2> /dev/termination-log", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "extra-files-0", + MountPath: "/etc/nginx/extra_files/myfile", + SubPath: "myfile", + ReadOnly: true, + }, + { + Name: "nginx-config", + MountPath: "/etc/nginx/nginx.conf", + SubPath: "nginx.conf", + ReadOnly: true, + }, + { + Name: "cache-vol", + MountPath: "/var/cache", + ReadOnly: false, + }, + { + Name: "nginx-certs-0", + MountPath: "/etc/nginx/certs/secret-tls", + ReadOnly: true, + }, + { + Name: "tls-session-tickets", + MountPath: "/etc/nginx/tickets", + ReadOnly: true, + }, + }, + }, + }, + + RestartPolicy: "Never", + }, + }, pod) +} diff --git a/go.mod b/go.mod index 9b7cf758..5ec0af82 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( k8s.io/client-go v0.26.2 k8s.io/kubectl v0.26.2 k8s.io/metrics v0.26.2 - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 sigs.k8s.io/controller-runtime v0.14.5 sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10 ) diff --git a/go.sum b/go.sum index 394f8299..da75bb1b 100644 --- a/go.sum +++ b/go.sum @@ -1454,6 +1454,8 @@ k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad h1:cYCDdgSMOKiCGm6v1vvR2v4l/naGorbwoJKE/e39BJI= knative.dev/pkg v0.0.0-20230306194819-b77a78c6c0ad/go.mod h1:S+KfTInuwEkZSTwvWqrWZV/TEw6ps51GUGaSC1Fnbe0= mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w=