From bc7f2f5adb3891f67a8c057a9b15845213cce769 Mon Sep 17 00:00:00 2001 From: Gerard Vanloo Date: Fri, 7 Oct 2022 08:51:16 -0400 Subject: [PATCH] operator: Add TLS profile support for Loki server and client HTTP and GRPC TLS options (#7322) --- operator/CHANGELOG.md | 1 + .../apis/config/v1/projectconfig_types.go | 17 +-- .../apis/config/v1/zz_generated.deepcopy.go | 20 --- ...-operator-manager-config_v1_configmap.yaml | 1 + operator/cmd/loki-broker/main.go | 38 +++-- .../openshift/controller_manager_config.yaml | 1 + .../controllers/loki/lokistack_controller.go | 5 + .../loki/lokistack_controller_test.go | 96 +++++++----- .../internal/tlsprofile/tlsprofile.go | 78 +++------- .../internal/tlsprofile/tlsprofile_test.go | 141 ++++++++++-------- .../handlers/lokistack_create_or_update.go | 25 ++-- operator/internal/manifests/build.go | 34 ++++- operator/internal/manifests/build_test.go | 111 ++++++++++++++ operator/internal/manifests/compactor.go | 21 ++- operator/internal/manifests/distributor.go | 28 ++-- operator/internal/manifests/gateway.go | 4 +- operator/internal/manifests/gateway_test.go | 6 +- operator/internal/manifests/indexgateway.go | 20 ++- operator/internal/manifests/ingester.go | 32 ++-- operator/internal/manifests/options.go | 24 ++- operator/internal/manifests/querier.go | 37 +++-- operator/internal/manifests/query-frontend.go | 40 +++-- .../internal/manifests/query-frontend_test.go | 9 +- operator/internal/manifests/ruler.go | 36 +++-- 24 files changed, 545 insertions(+), 280 deletions(-) diff --git a/operator/CHANGELOG.md b/operator/CHANGELOG.md index ca4d894820..5c15b483b9 100644 --- a/operator/CHANGELOG.md +++ b/operator/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main +- [7322](https://github.com/grafana/loki/pull/7322) **Red-GV**: Configuring server and client HTTP and GRPC TLS options - [7272](https://github.com/grafana/loki/pull/7272) **aminesnow**: Use cluster monitoring alertmanager by default on openshift clusters - [7295](https://github.com/grafana/loki/pull/7295) **xperimental**: Add extended-validation for rules on OpenShift - [6951](https://github.com/grafana/loki/pull/6951) **Red-GV**: Adding operational Lokistack alerts diff --git a/operator/apis/config/v1/projectconfig_types.go b/operator/apis/config/v1/projectconfig_types.go index 1949164932..8b553b6f20 100644 --- a/operator/apis/config/v1/projectconfig_types.go +++ b/operator/apis/config/v1/projectconfig_types.go @@ -21,6 +21,10 @@ type OpenShiftFeatureGates struct { // ExtendedRuleValidation enables extended validation of AlertingRule and RecordingRule // to enforce tenancy in an OpenShift context. ExtendedRuleValidation bool `json:"ruleExtendedValidation,omitempty"` + + // ClusterTLSPolicy enables usage of TLS policies set in the API Server. + // More details: https://docs.openshift.com/container-platform/4.11/security/tls-security-profiles.html + ClusterTLSPolicy bool `json:"clusterTLSPolicy,omitempty"` } // FeatureGates is the supported set of all operator feature gates. @@ -78,7 +82,8 @@ type FeatureGates struct { // OpenShift contains a set of feature gates supported only on OpenShift. OpenShift OpenShiftFeatureGates `json:"openshift,omitempty"` - // TLSProfile allows to chose a TLS security profile. + // TLSProfile allows to chose a TLS security profile. Enforced + // when using HTTPEncryption or GRPCEncryption. TLSProfile string `json:"tlsProfile,omitempty"` } @@ -98,16 +103,6 @@ const ( TLSProfileModernType TLSProfileType = "Modern" ) -// TLSProfileSpec is the desired behavior of a TLSProfileType. -type TLSProfileSpec struct { - // ciphers is used to specify the cipher algorithms that are negotiated - // during the TLS handshake. - Ciphers []string - // minTLSVersion is used to specify the minimal version of the TLS protocol - // that is negotiated during the TLS handshake. - MinTLSVersion string -} - //+kubebuilder:object:root=true // ProjectConfig is the Schema for the projectconfigs API diff --git a/operator/apis/config/v1/zz_generated.deepcopy.go b/operator/apis/config/v1/zz_generated.deepcopy.go index 316a973a93..3ff10850cd 100644 --- a/operator/apis/config/v1/zz_generated.deepcopy.go +++ b/operator/apis/config/v1/zz_generated.deepcopy.go @@ -65,23 +65,3 @@ func (in *ProjectConfig) DeepCopyObject() runtime.Object { } return nil } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSProfileSpec) DeepCopyInto(out *TLSProfileSpec) { - *out = *in - if in.Ciphers != nil { - in, out := &in.Ciphers, &out.Ciphers - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSProfileSpec. -func (in *TLSProfileSpec) DeepCopy() *TLSProfileSpec { - if in == nil { - return nil - } - out := new(TLSProfileSpec) - in.DeepCopyInto(out) - return out -} diff --git a/operator/bundle/manifests/loki-operator-manager-config_v1_configmap.yaml b/operator/bundle/manifests/loki-operator-manager-config_v1_configmap.yaml index 96699b3327..7b03313b8e 100644 --- a/operator/bundle/manifests/loki-operator-manager-config_v1_configmap.yaml +++ b/operator/bundle/manifests/loki-operator-manager-config_v1_configmap.yaml @@ -46,6 +46,7 @@ data: servingCertsService: true gatewayRoute: true ruleExtendedValidation: true + clusterTLSPolicy: true kind: ConfigMap metadata: labels: diff --git a/operator/cmd/loki-broker/main.go b/operator/cmd/loki-broker/main.go index c4c7722be9..b282e33866 100644 --- a/operator/cmd/loki-broker/main.go +++ b/operator/cmd/loki-broker/main.go @@ -7,13 +7,14 @@ import ( "path" "strings" - "github.com/ViaQ/logerr/v2/log" - "github.com/go-logr/logr" configv1 "github.com/grafana/loki/operator/apis/config/v1" - projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests" "github.com/grafana/loki/operator/internal/manifests/storage" + + "github.com/ViaQ/logerr/v2/log" + "github.com/go-logr/logr" + openshiftv1 "github.com/openshift/api/config/v1" "sigs.k8s.io/yaml" ) @@ -129,22 +130,21 @@ func main() { } if cfg.featureFlags.TLSProfile != "" && - cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileOldType) && - cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileIntermediateType) && - cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileModernType) { + cfg.featureFlags.TLSProfile != string(configv1.TLSProfileOldType) && + cfg.featureFlags.TLSProfile != string(configv1.TLSProfileIntermediateType) && + cfg.featureFlags.TLSProfile != string(configv1.TLSProfileModernType) { logger.Error(err, "failed to parse TLS profile. Allowed values: 'Old', 'Intermediate', 'Modern'", "value", cfg.featureFlags.TLSProfile) os.Exit(1) } // Convert config to manifest.Options opts := manifests.Options{ - Name: cfg.Name, - Namespace: cfg.Namespace, - Image: cfg.Image, - Stack: ls.Spec, - Gates: cfg.featureFlags, - ObjectStorage: cfg.objectStorage, - TLSProfileType: projectconfigv1.TLSProfileType(cfg.featureFlags.TLSProfile), + Name: cfg.Name, + Namespace: cfg.Namespace, + Image: cfg.Image, + Stack: ls.Spec, + Gates: cfg.featureFlags, + ObjectStorage: cfg.objectStorage, } if optErr := manifests.ApplyDefaultSettings(&opts); optErr != nil { @@ -152,6 +152,18 @@ func main() { os.Exit(1) } + var tlsSecurityProfile *openshiftv1.TLSSecurityProfile = nil + if cfg.featureFlags.TLSProfile != "" { + tlsSecurityProfile = &openshiftv1.TLSSecurityProfile{ + Type: openshiftv1.TLSProfileType(cfg.featureFlags.TLSProfile), + } + } + + if optErr := manifests.ApplyTLSSettings(&opts, tlsSecurityProfile); optErr != nil { + logger.Error(optErr, "failed to conform options to tls profile settings") + os.Exit(1) + } + objects, err := manifests.BuildAll(opts) if err != nil { logger.Error(err, "failed to build manifests") diff --git a/operator/config/overlays/openshift/controller_manager_config.yaml b/operator/config/overlays/openshift/controller_manager_config.yaml index 12c0760257..c69ada2e8e 100644 --- a/operator/config/overlays/openshift/controller_manager_config.yaml +++ b/operator/config/overlays/openshift/controller_manager_config.yaml @@ -43,3 +43,4 @@ featureGates: servingCertsService: true gatewayRoute: true ruleExtendedValidation: true + clusterTLSPolicy: true diff --git a/operator/controllers/loki/lokistack_controller.go b/operator/controllers/loki/lokistack_controller.go index 9acd6c8b08..d1afaa31a7 100644 --- a/operator/controllers/loki/lokistack_controller.go +++ b/operator/controllers/loki/lokistack_controller.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/loki/operator/internal/status" routev1 "github.com/openshift/api/route/v1" + openshiftconfigv1 "github.com/openshift/api/config/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" @@ -174,5 +175,9 @@ func (r *LokiStackReconciler) buildController(bld k8s.Builder) error { bld = bld.Owns(&networkingv1.Ingress{}, updateOrDeleteOnlyPred) } + if r.FeatureGates.OpenShift.ClusterTLSPolicy { + bld = bld.Owns(&openshiftconfigv1.APIServer{}, updateOrDeleteOnlyPred) + } + return bld.Complete(r) } diff --git a/operator/controllers/loki/lokistack_controller_test.go b/operator/controllers/loki/lokistack_controller_test.go index b098f26295..91f2c8bb18 100644 --- a/operator/controllers/loki/lokistack_controller_test.go +++ b/operator/controllers/loki/lokistack_controller_test.go @@ -12,6 +12,7 @@ import ( "github.com/ViaQ/logerr/v2/log" "github.com/go-logr/logr" + openshiftconfigv1 "github.com/openshift/api/config/v1" routev1 "github.com/openshift/api/route/v1" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -74,63 +75,74 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test // Require owned resources type test struct { - obj client.Object - index int - featureGates configv1.FeatureGates - pred builder.OwnsOption + obj client.Object + index int + ownCallsCount int + featureGates configv1.FeatureGates + pred builder.OwnsOption } table := []test{ { - obj: &corev1.ConfigMap{}, - index: 0, - pred: updateOrDeleteOnlyPred, + obj: &corev1.ConfigMap{}, + index: 0, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &corev1.ServiceAccount{}, - index: 1, - pred: updateOrDeleteOnlyPred, + obj: &corev1.ServiceAccount{}, + index: 1, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &corev1.Service{}, - index: 2, - pred: updateOrDeleteOnlyPred, + obj: &corev1.Service{}, + index: 2, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &appsv1.Deployment{}, - index: 3, - pred: updateOrDeleteOnlyPred, + obj: &appsv1.Deployment{}, + index: 3, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &appsv1.StatefulSet{}, - index: 4, - pred: updateOrDeleteOnlyPred, + obj: &appsv1.StatefulSet{}, + index: 4, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &rbacv1.ClusterRole{}, - index: 5, - pred: updateOrDeleteOnlyPred, + obj: &rbacv1.ClusterRole{}, + index: 5, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &rbacv1.ClusterRoleBinding{}, - index: 6, - pred: updateOrDeleteOnlyPred, + obj: &rbacv1.ClusterRoleBinding{}, + index: 6, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &rbacv1.Role{}, - index: 7, - pred: updateOrDeleteOnlyPred, + obj: &rbacv1.Role{}, + index: 7, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, { - obj: &rbacv1.RoleBinding{}, - index: 8, - pred: updateOrDeleteOnlyPred, + obj: &rbacv1.RoleBinding{}, + index: 8, + ownCallsCount: 10, + pred: updateOrDeleteOnlyPred, }, // The next two share the same index, because the // controller either reconciles an Ingress (i.e. Kubernetes) // or a Route (i.e. OpenShift). { - obj: &networkingv1.Ingress{}, - index: 9, + obj: &networkingv1.Ingress{}, + index: 9, + ownCallsCount: 10, featureGates: configv1.FeatureGates{ OpenShift: configv1.OpenShiftFeatureGates{ GatewayRoute: false, @@ -139,8 +151,9 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test pred: updateOrDeleteOnlyPred, }, { - obj: &routev1.Route{}, - index: 9, + obj: &routev1.Route{}, + index: 9, + ownCallsCount: 10, featureGates: configv1.FeatureGates{ OpenShift: configv1.OpenShiftFeatureGates{ GatewayRoute: true, @@ -148,6 +161,17 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test }, pred: updateOrDeleteOnlyPred, }, + { + obj: &openshiftconfigv1.APIServer{}, + index: 10, + ownCallsCount: 11, + featureGates: configv1.FeatureGates{ + OpenShift: configv1.OpenShiftFeatureGates{ + ClusterTLSPolicy: true, + }, + }, + pred: updateOrDeleteOnlyPred, + }, } for _, tst := range table { b := &k8sfakes.FakeBuilder{} @@ -159,7 +183,7 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test require.NoError(t, err) // Require Owns-Calls for all owned resources - require.Equal(t, 10, b.OwnsCallCount()) + require.Equal(t, tst.ownCallsCount, b.OwnsCallCount()) // Require Owns-call options to have delete predicate only obj, opts := b.OwnsArgsForCall(tst.index) diff --git a/operator/internal/handlers/internal/tlsprofile/tlsprofile.go b/operator/internal/handlers/internal/tlsprofile/tlsprofile.go index 6ba42da1c8..9fb1413881 100644 --- a/operator/internal/handlers/internal/tlsprofile/tlsprofile.go +++ b/operator/internal/handlers/internal/tlsprofile/tlsprofile.go @@ -3,69 +3,37 @@ package tlsprofile import ( "context" - "github.com/go-logr/logr" - projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1" + configv1 "github.com/grafana/loki/operator/apis/config/v1" + + "github.com/ViaQ/logerr/v2/kverrors" "github.com/grafana/loki/operator/internal/external/k8s" - openshiftv1 "github.com/openshift/api/config/v1" - "github.com/openshift/library-go/pkg/crypto" + openshiftconfigv1 "github.com/openshift/api/config/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) // APIServerName is the apiserver resource name used to fetch it. const APIServerName = "cluster" -// GetSecurityProfileInfo gets the tls profile info to apply. -func GetSecurityProfileInfo(ctx context.Context, k k8s.Client, log logr.Logger, tlsProfileType projectconfigv1.TLSProfileType) (projectconfigv1.TLSProfileSpec, error) { - var tlsProfile openshiftv1.TLSSecurityProfile - - if tlsProfileType != "" { - tlsProfile = openshiftv1.TLSSecurityProfile{ - Type: openshiftv1.TLSProfileType(tlsProfileType), - } - } else { - tlsProfile = openshiftv1.TLSSecurityProfile{ - Type: openshiftv1.TLSProfileIntermediateType, - } - - var apiServer openshiftv1.APIServer +// GetTLSSecurityProfile gets the tls profile info to apply. +func GetTLSSecurityProfile(ctx context.Context, k k8s.Client, tlsProfileType configv1.TLSProfileType) (*openshiftconfigv1.TLSSecurityProfile, error) { + switch tlsProfileType { + case configv1.TLSProfileOldType: + return &openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileOldType, + }, nil + case configv1.TLSProfileIntermediateType: + return &openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileIntermediateType, + }, nil + case configv1.TLSProfileModernType: + return &openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileModernType, + }, nil + default: + var apiServer openshiftconfigv1.APIServer if err := k.Get(ctx, client.ObjectKey{Name: APIServerName}, &apiServer); err != nil { - log.Error(err, "failed to lookup apiServer. Using Intermediate profile") - } - - if apiServer.Spec.TLSSecurityProfile != nil { - tlsProfile = *apiServer.Spec.TLSSecurityProfile + return nil, kverrors.Wrap(err, "failed to lookup openshift apiServer") } + return apiServer.Spec.TLSSecurityProfile, nil } - - tlsMinVersion, ciphers := extractInfoFromTLSProfile(&tlsProfile) - return projectconfigv1.TLSProfileSpec{ - MinTLSVersion: tlsMinVersion, - Ciphers: ciphers, - }, nil -} - -func extractInfoFromTLSProfile(profile *openshiftv1.TLSSecurityProfile) (string, []string) { - var profileType openshiftv1.TLSProfileType - if profile == nil { - profileType = openshiftv1.TLSProfileIntermediateType - } else { - profileType = profile.Type - } - - var profileSpec *openshiftv1.TLSProfileSpec - if profileType == openshiftv1.TLSProfileCustomType { - if profile.Custom != nil { - profileSpec = &profile.Custom.TLSProfileSpec - } - } else { - profileSpec = openshiftv1.TLSProfiles[profileType] - } - - // nothing found / custom type set but no actual custom spec - if profileSpec == nil { - profileSpec = openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType] - } - - // need to remap all Ciphers to their respective IANA names used by Go - return string(profileSpec.MinTLSVersion), crypto.OpenSSLToIANACipherSuites(profileSpec.Ciphers) } diff --git a/operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go b/operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go index 2bf61a518f..ac3a4f7be7 100644 --- a/operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go +++ b/operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/go-logr/logr" - projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1" + configv1 "github.com/grafana/loki/operator/apis/config/v1" "github.com/grafana/loki/operator/internal/external/k8s/k8sfakes" "github.com/grafana/loki/operator/internal/handlers/internal/tlsprofile" - openshiftv1 "github.com/openshift/api/config/v1" + + openshiftconfigv1 "github.com/openshift/api/config/v1" "github.com/stretchr/testify/assert" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -17,78 +17,43 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -var ( - apiServer = openshiftv1.APIServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster", - }, - } - ciphersOld = []string{ - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA256", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - } - ciphersIntermediate = []string{ - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - } -) - -func TestGetSecurityProfileInfo(t *testing.T) { +func TestGetTLSSecurityProfile(t *testing.T) { type tt struct { desc string - profile projectconfigv1.TLSProfileType - expected projectconfigv1.TLSProfileSpec + profile configv1.TLSProfileType + expected openshiftconfigv1.TLSSecurityProfile } tc := []tt{ { desc: "Old profile", - profile: projectconfigv1.TLSProfileOldType, - expected: projectconfigv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS10", - Ciphers: ciphersOld, + profile: configv1.TLSProfileOldType, + expected: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileOldType, }, }, { desc: "Intermediate profile", - profile: projectconfigv1.TLSProfileIntermediateType, - expected: projectconfigv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS12", - Ciphers: ciphersIntermediate, + profile: configv1.TLSProfileIntermediateType, + expected: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileIntermediateType, }, }, { desc: "Modern profile", - profile: projectconfigv1.TLSProfileModernType, - expected: projectconfigv1.TLSProfileSpec{ - MinTLSVersion: "VersionTLS13", - // Go lib crypto doesn't allow ciphers to be configured for TLS 1.3 - // (Read this and weep: https://github.com/golang/go/issues/29349) - Ciphers: []string{}, + profile: configv1.TLSProfileModernType, + expected: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileModernType, }, }, } + apiServer := openshiftconfigv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + }, + } + sw := &k8sfakes.FakeStatusWriter{} k := &k8sfakes.FakeClient{} @@ -106,10 +71,68 @@ func TestGetSecurityProfileInfo(t *testing.T) { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() - info, err := tlsprofile.GetSecurityProfileInfo(context.TODO(), k, logr.Logger{}, tc.profile) + + profile, err := tlsprofile.GetTLSSecurityProfile(context.TODO(), k, tc.profile) + assert.Nil(t, err) - assert.NotNil(t, info) - assert.EqualValues(t, tc.expected, info) + assert.NotNil(t, profile) + assert.EqualValues(t, &tc.expected, profile) }) } } + +func TestGetTLSSecurityProfile_CustomProfile(t *testing.T) { + sw := &k8sfakes.FakeStatusWriter{} + k := &k8sfakes.FakeClient{} + + tlsCustomProfile := &openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileCustomType, + Custom: &openshiftconfigv1.CustomTLSProfile{ + TLSProfileSpec: openshiftconfigv1.TLSProfileSpec{ + Ciphers: []string{"custom-cipher"}, + MinTLSVersion: "VersionTLS12", + }, + }, + } + + apiServer := openshiftconfigv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + }, + Spec: openshiftconfigv1.APIServerSpec{ + TLSSecurityProfile: tlsCustomProfile, + }, + } + + k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error { + if apiServer.Name == name.Name { + k.SetClientObject(object, &apiServer) + return nil + } + return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") + } + + k.StatusStub = func() client.StatusWriter { return sw } + + profile, err := tlsprofile.GetTLSSecurityProfile(context.TODO(), k, configv1.TLSProfileType("custom")) + + assert.Nil(t, err) + assert.NotNil(t, profile) + assert.EqualValues(t, tlsCustomProfile, profile) +} + +func TestGetTLSSecurityProfile_APIServerNotFound(t *testing.T) { + sw := &k8sfakes.FakeStatusWriter{} + k := &k8sfakes.FakeClient{} + + k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error { + return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") + } + + k.StatusStub = func() client.StatusWriter { return sw } + + profile, err := tlsprofile.GetTLSSecurityProfile(context.TODO(), k, "") + + assert.NotNil(t, err) + assert.Nil(t, profile) +} diff --git a/operator/internal/handlers/lokistack_create_or_update.go b/operator/internal/handlers/lokistack_create_or_update.go index fda5cf3e65..52df276ab0 100644 --- a/operator/internal/handlers/lokistack_create_or_update.go +++ b/operator/internal/handlers/lokistack_create_or_update.go @@ -7,7 +7,6 @@ import ( "time" configv1 "github.com/grafana/loki/operator/apis/config/v1" - projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" "github.com/grafana/loki/operator/internal/external/k8s" @@ -175,12 +174,12 @@ func CreateOrUpdateLokiStack( if stack.Spec.Rules != nil && stack.Spec.Rules.Enabled { alertingRules, recordingRules, err = rules.List(ctx, k, req.Namespace, stack.Spec.Rules) if err != nil { - log.Error(err, "failed to lookup rules", "spec", stack.Spec.Rules) + ll.Error(err, "failed to lookup rules", "spec", stack.Spec.Rules) } rulerConfig, err = rules.GetRulerConfig(ctx, k, req) if err != nil { - log.Error(err, "failed to lookup ruler config", "key", req.NamespacedName) + ll.Error(err, "failed to lookup ruler config", "key", req.NamespacedName) } if rulerConfig != nil && rulerConfig.RemoteWriteSpec != nil && rulerConfig.RemoteWriteSpec.ClientSpec != nil { @@ -235,7 +234,6 @@ func CreateOrUpdateLokiStack( Secrets: tenantSecrets, Configs: tenantConfigs, }, - TLSProfileType: projectconfigv1.TLSProfileType(fg.TLSProfile), OpenShiftOptions: manifests_openshift.Options{ BuildOpts: manifests_openshift.BuildOptions{ AlertManagerEnabled: ocpAmEnabled, @@ -252,18 +250,27 @@ func CreateOrUpdateLokiStack( if fg.LokiStackGateway { if optErr := manifests.ApplyGatewayDefaultOptions(&opts); optErr != nil { - ll.Error(optErr, "failed to apply defaults options to gateway settings ") + ll.Error(optErr, "failed to apply defaults options to gateway settings") return optErr } } - spec, err := tlsprofile.GetSecurityProfileInfo(ctx, k, ll, opts.TLSProfileType) + tlsProfileType := configv1.TLSProfileType(fg.TLSProfile) + // Overwrite the profile from the flags and use the profile from the apiserver instead + if fg.OpenShift.ClusterTLSPolicy { + tlsProfileType = configv1.TLSProfileType("") + } + + tlsProfile, err := tlsprofile.GetTLSSecurityProfile(ctx, k, tlsProfileType) if err != nil { - ll.Error(err, "failed to get security profile info") - return err + // The API server is not guaranteed to be there nor have a result. + ll.Error(err, "failed to get security profile. will use default tls profile.") } - opts.TLSProfileSpec = spec + if optErr := manifests.ApplyTLSSettings(&opts, tlsProfile); optErr != nil { + ll.Error(optErr, "failed to conform options to tls profile settings") + return optErr + } objects, err := manifests.BuildAll(opts) if err != nil { diff --git a/operator/internal/manifests/build.go b/operator/internal/manifests/build.go index 9cea54d65e..f27d350d9d 100644 --- a/operator/internal/manifests/build.go +++ b/operator/internal/manifests/build.go @@ -1,11 +1,13 @@ package manifests import ( - "github.com/ViaQ/logerr/v2/kverrors" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests/internal" + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" + openshiftconfigv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/crypto" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -137,3 +139,33 @@ func ApplyDefaultSettings(opts *Options) error { return nil } + +// ApplyTLSSettings manipulates the options to conform to the +// TLS profile specifications +func ApplyTLSSettings(opts *Options, profile *openshiftconfigv1.TLSSecurityProfile) error { + tlsSecurityProfile := &openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileIntermediateType, + } + + if profile != nil { + tlsSecurityProfile = profile + } + + profileSpec, ok := openshiftconfigv1.TLSProfiles[tlsSecurityProfile.Type] + + if !ok { + return kverrors.New("unable to determine tls profile settings") + } + + if tlsSecurityProfile.Type == openshiftconfigv1.TLSProfileCustomType && tlsSecurityProfile.Custom != nil { + profileSpec = &tlsSecurityProfile.Custom.TLSProfileSpec + } + + // need to remap all ciphers to their respective IANA names used by Go + opts.TLSProfile = TLSProfileSpec{ + MinTLSVersion: string(profileSpec.MinTLSVersion), + Ciphers: crypto.OpenSSLToIANACipherSuites(profileSpec.Ciphers), + } + + return nil +} diff --git a/operator/internal/manifests/build_test.go b/operator/internal/manifests/build_test.go index e22338499e..5d8a042446 100644 --- a/operator/internal/manifests/build_test.go +++ b/operator/internal/manifests/build_test.go @@ -2,8 +2,10 @@ package manifests import ( "fmt" + "strings" "testing" + openshiftconfigv1 "github.com/openshift/api/config/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -11,6 +13,7 @@ import ( configv1 "github.com/grafana/loki/operator/apis/config/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests/internal" + "github.com/stretchr/testify/require" ) @@ -83,6 +86,88 @@ func TestApplyUserOptions_AlwaysSetCompactorReplicasToOne(t *testing.T) { } } +func TestApplyTLSSettings_OverrideDefaults(t *testing.T) { + type tt struct { + desc string + profile openshiftconfigv1.TLSSecurityProfile + expected TLSProfileSpec + } + + tc := []tt{ + { + desc: "Old profile", + profile: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileOldType, + }, + expected: TLSProfileSpec{ + MinTLSVersion: "VersionTLS10", + Ciphers: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + }, + }, + }, + { + desc: "Intermediate profile", + profile: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileIntermediateType, + }, + expected: TLSProfileSpec{ + MinTLSVersion: "VersionTLS12", + Ciphers: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + }, + }, + }, + { + desc: "Modern profile", + profile: openshiftconfigv1.TLSSecurityProfile{ + Type: openshiftconfigv1.TLSProfileModernType, + }, + expected: TLSProfileSpec{ + MinTLSVersion: "VersionTLS13", + // Go lib crypto doesn't allow ciphers to be configured for TLS 1.3 + // (Read this and weep: https://github.com/golang/go/issues/29349) + Ciphers: []string{}, + }, + }, + } + + for _, tc := range tc { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + opts := Options{} + err := ApplyTLSSettings(&opts, &tc.profile) + + require.Nil(t, err) + require.EqualValues(t, tc.expected, opts.TLSProfile) + }) + } +} + func TestBuildAll_WithFeatureGates_ServiceMonitors(t *testing.T) { type test struct { desc string @@ -242,9 +327,19 @@ func TestBuildAll_WithFeatureGates_HTTPEncryption(t *testing.T) { HTTPEncryption: true, }, } + ciphers := strings.Join([]string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + }, ",") err := ApplyDefaultSettings(&opts) require.NoError(t, err) + err = ApplyTLSSettings(&opts, nil) + require.NoError(t, err) objects, buildErr := BuildAll(opts) require.NoError(t, buildErr) @@ -295,6 +390,8 @@ func TestBuildAll_WithFeatureGates_HTTPEncryption(t *testing.T) { } require.Contains(t, vms, expVolumeMount) + require.Contains(t, args, "-server.tls-min-version=VersionTLS12") + require.Contains(t, args, fmt.Sprintf("-server.tls-cipher-suites=%s", ciphers)) require.Contains(t, args, "-server.http-tls-cert-path=/var/run/tls/http/tls.crt") require.Contains(t, args, "-server.http-tls-key-path=/var/run/tls/http/tls.key") require.Equal(t, corev1.URISchemeHTTPS, rps) @@ -484,6 +581,15 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { "test-ruler": "test-ruler-grpc", } + ciphers := strings.Join([]string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + }, ",") + for _, tst := range table { tst := tst t.Run(tst.desc, func(t *testing.T) { @@ -492,6 +598,9 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { err := ApplyDefaultSettings(&tst.BuildOptions) require.NoError(t, err) + err = ApplyTLSSettings(&tst.BuildOptions, nil) + require.NoError(t, err) + objs, err := BuildAll(tst.BuildOptions) require.NoError(t, err) @@ -516,6 +625,8 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { args := []string{ "-server.grpc-tls-cert-path=/var/run/tls/grpc/tls.crt", "-server.grpc-tls-key-path=/var/run/tls/grpc/tls.key", + "-server.tls-min-version=VersionTLS12", + fmt.Sprintf("-server.tls-cipher-suites=%s", ciphers), } vm := corev1.VolumeMount{ diff --git a/operator/internal/manifests/compactor.go b/operator/internal/manifests/compactor.go index 6b5a9d1783..11874f36ba 100644 --- a/operator/internal/manifests/compactor.go +++ b/operator/internal/manifests/compactor.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/storage" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -13,7 +14,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -21,7 +21,7 @@ import ( func BuildCompactor(opts Options) ([]client.Object, error) { statefulSet := NewCompactorStatefulSet(opts) if opts.Gates.HTTPEncryption { - if err := configureCompactorHTTPServicePKI(statefulSet, opts.Name); err != nil { + if err := configureCompactorHTTPServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -31,7 +31,7 @@ func BuildCompactor(opts Options) ([]client.Object, error) { } if opts.Gates.GRPCEncryption { - if err := configureCompactorGRPCServicePKI(statefulSet, opts.Name); err != nil { + if err := configureCompactorGRPCServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -108,6 +108,13 @@ func NewCompactorStatefulSet(opts Options) *appsv1.StatefulSet { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.Compactor != nil { podSpec.Tolerations = opts.Stack.Template.Compactor.Tolerations podSpec.NodeSelector = opts.Stack.Template.Compactor.NodeSelector @@ -223,12 +230,12 @@ func NewCompactorHTTPService(opts Options) *corev1.Service { } } -func configureCompactorHTTPServicePKI(statefulSet *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameCompactorHTTP(stackName) +func configureCompactorHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameCompactorHTTP(opts.Name) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) } -func configureCompactorGRPCServicePKI(sts *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameCompactorGRPC(stackName) +func configureCompactorGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameCompactorGRPC(opts.Name) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) } diff --git a/operator/internal/manifests/distributor.go b/operator/internal/manifests/distributor.go index 46ffb7b433..174bb6dde3 100644 --- a/operator/internal/manifests/distributor.go +++ b/operator/internal/manifests/distributor.go @@ -4,8 +4,9 @@ import ( "fmt" "path" - "github.com/ViaQ/logerr/v2/kverrors" "github.com/grafana/loki/operator/internal/manifests/internal/config" + + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -20,13 +21,13 @@ import ( func BuildDistributor(opts Options) ([]client.Object, error) { deployment := NewDistributorDeployment(opts) if opts.Gates.HTTPEncryption { - if err := configureDistributorHTTPServicePKI(deployment, opts.Name); err != nil { + if err := configureDistributorHTTPServicePKI(deployment, opts); err != nil { return nil, err } } if opts.Gates.GRPCEncryption { - if err := configureDistributorGRPCServicePKI(deployment, opts.Name, opts.Namespace); err != nil { + if err := configureDistributorGRPCServicePKI(deployment, opts); err != nil { return nil, err } } @@ -103,6 +104,13 @@ func NewDistributorDeployment(opts Options) *appsv1.Deployment { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.Distributor != nil { podSpec.Tolerations = opts.Stack.Template.Distributor.Tolerations podSpec.NodeSelector = opts.Stack.Template.Distributor.NodeSelector @@ -199,13 +207,13 @@ func NewDistributorHTTPService(opts Options) *corev1.Service { } } -func configureDistributorHTTPServicePKI(deployment *appsv1.Deployment, stackName string) error { - serviceName := serviceNameDistributorHTTP(stackName) +func configureDistributorHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNameDistributorHTTP(opts.Name) return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) } -func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, stackName, stackNS string) error { - caBundleName := signingCABundleName(stackName) +func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error { + caBundleName := signingCABundleName(opts.Name) secretVolumeSpec := corev1.PodSpec{ Volumes: []corev1.Volume{ { @@ -232,8 +240,10 @@ func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, stackName Args: []string{ // Enable GRPC over TLS for ingester client "-ingester.client.tls-enabled=true", + fmt.Sprintf("-ingester.client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-ingester.client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-ingester.client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(stackName), stackNS)), + fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(opts.Name), opts.Namespace)), }, } @@ -245,6 +255,6 @@ func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, stackName return kverrors.Wrap(err, "failed to merge container") } - serviceName := serviceNameDistributorGRPC(stackName) + serviceName := serviceNameDistributorGRPC(opts.Name) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) } diff --git a/operator/internal/manifests/gateway.go b/operator/internal/manifests/gateway.go index f8b80b77f4..39e323d855 100644 --- a/operator/internal/manifests/gateway.go +++ b/operator/internal/manifests/gateway.go @@ -42,8 +42,8 @@ func BuildGateway(opts Options) ([]client.Object, error) { objs := []client.Object{cm, dpl, svc, ing} - minTLSVersion := opts.TLSProfileSpec.MinTLSVersion - ciphersList := opts.TLSProfileSpec.Ciphers + minTLSVersion := opts.TLSProfile.MinTLSVersion + ciphersList := opts.TLSProfile.Ciphers ciphers := strings.Join(ciphersList, `,`) if opts.Gates.HTTPEncryption { diff --git a/operator/internal/manifests/gateway_test.go b/operator/internal/manifests/gateway_test.go index bc47528a4e..82cd8003a2 100644 --- a/operator/internal/manifests/gateway_test.go +++ b/operator/internal/manifests/gateway_test.go @@ -296,7 +296,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) { HTTPEncryption: true, TLSProfile: string(configv1.TLSProfileOldType), }, - TLSProfileSpec: configv1.TLSProfileSpec{ + TLSProfile: TLSProfileSpec{ MinTLSVersion: "min-version", Ciphers: []string{"cipher1", "cipher2"}, }, @@ -348,7 +348,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) { HTTPEncryption: true, TLSProfile: string(configv1.TLSProfileOldType), }, - TLSProfileSpec: configv1.TLSProfileSpec{ + TLSProfile: TLSProfileSpec{ MinTLSVersion: "min-version", Ciphers: []string{"cipher1", "cipher2"}, }, @@ -378,7 +378,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) { HTTPEncryption: true, TLSProfile: string(configv1.TLSProfileOldType), }, - TLSProfileSpec: configv1.TLSProfileSpec{ + TLSProfile: TLSProfileSpec{ MinTLSVersion: "min-version", Ciphers: []string{"cipher1", "cipher2"}, }, diff --git a/operator/internal/manifests/indexgateway.go b/operator/internal/manifests/indexgateway.go index 63fa4e6876..5c3a685930 100644 --- a/operator/internal/manifests/indexgateway.go +++ b/operator/internal/manifests/indexgateway.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/storage" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -20,7 +21,7 @@ import ( func BuildIndexGateway(opts Options) ([]client.Object, error) { statefulSet := NewIndexGatewayStatefulSet(opts) if opts.Gates.HTTPEncryption { - if err := configureIndexGatewayHTTPServicePKI(statefulSet, opts.Name); err != nil { + if err := configureIndexGatewayHTTPServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -30,7 +31,7 @@ func BuildIndexGateway(opts Options) ([]client.Object, error) { } if opts.Gates.GRPCEncryption { - if err := configureIndexGatewayGRPCServicePKI(statefulSet, opts.Name); err != nil { + if err := configureIndexGatewayGRPCServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -107,6 +108,13 @@ func NewIndexGatewayStatefulSet(opts Options) *appsv1.StatefulSet { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.IndexGateway != nil { podSpec.Tolerations = opts.Stack.Template.IndexGateway.Tolerations podSpec.NodeSelector = opts.Stack.Template.IndexGateway.NodeSelector @@ -223,12 +231,12 @@ func NewIndexGatewayHTTPService(opts Options) *corev1.Service { } } -func configureIndexGatewayHTTPServicePKI(statefulSet *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameIndexGatewayHTTP(stackName) +func configureIndexGatewayHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameIndexGatewayHTTP(opts.Name) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) } -func configureIndexGatewayGRPCServicePKI(sts *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameIndexGatewayGRPC(stackName) +func configureIndexGatewayGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameIndexGatewayGRPC(opts.Name) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) } diff --git a/operator/internal/manifests/ingester.go b/operator/internal/manifests/ingester.go index 58cd7e9a78..58ab9c98b8 100644 --- a/operator/internal/manifests/ingester.go +++ b/operator/internal/manifests/ingester.go @@ -4,12 +4,11 @@ import ( "fmt" "path" - "github.com/ViaQ/logerr/v2/kverrors" "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/storage" + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -24,7 +23,7 @@ import ( func BuildIngester(opts Options) ([]client.Object, error) { statefulSet := NewIngesterStatefulSet(opts) if opts.Gates.HTTPEncryption { - if err := configureIngesterHTTPServicePKI(statefulSet, opts.Name); err != nil { + if err := configureIngesterHTTPServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -34,7 +33,7 @@ func BuildIngester(opts Options) ([]client.Object, error) { } if opts.Gates.GRPCEncryption { - if err := configureIngesterGRPCServicePKI(statefulSet, opts.Name, opts.Namespace); err != nil { + if err := configureIngesterGRPCServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -121,6 +120,13 @@ func NewIngesterStatefulSet(opts Options) *appsv1.StatefulSet { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.Ingester != nil { podSpec.Tolerations = opts.Stack.Template.Ingester.Tolerations podSpec.NodeSelector = opts.Stack.Template.Ingester.NodeSelector @@ -255,13 +261,13 @@ func NewIngesterHTTPService(opts Options) *corev1.Service { } } -func configureIngesterHTTPServicePKI(statefulSet *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameIngesterHTTP(stackName) +func configureIngesterHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameIngesterHTTP(opts.Name) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) } -func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNS string) error { - caBundleName := signingCABundleName(stackName) +func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error { + caBundleName := signingCABundleName(opts.Name) secretVolumeSpec := corev1.PodSpec{ Volumes: []corev1.Volume{ { @@ -288,12 +294,16 @@ func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNS Args: []string{ // Enable GRPC over TLS for ingester client "-ingester.client.tls-enabled=true", + fmt.Sprintf("-ingester.client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-ingester.client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-ingester.client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(stackName), stackNS)), + fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(opts.Name), opts.Namespace)), // Enable GRPC over TLS for boltb-shipper index-gateway client "-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(stackName), stackNS)), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(opts.Name), opts.Namespace)), }, } @@ -305,6 +315,6 @@ func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNS return kverrors.Wrap(err, "failed to merge container") } - serviceName := serviceNameIngesterGRPC(stackName) + serviceName := serviceNameIngesterGRPC(opts.Name) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) } diff --git a/operator/internal/manifests/options.go b/operator/internal/manifests/options.go index fdf60a7695..ea7c48638d 100644 --- a/operator/internal/manifests/options.go +++ b/operator/internal/manifests/options.go @@ -1,8 +1,9 @@ package manifests import ( + "strings" + configv1 "github.com/grafana/loki/operator/apis/config/v1" - projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" "github.com/grafana/loki/operator/internal/manifests/internal" @@ -20,9 +21,6 @@ type Options struct { GatewayBaseDomain string ConfigSHA1 string - TLSProfileType projectconfigv1.TLSProfileType - TLSProfileSpec projectconfigv1.TLSProfileSpec - Gates configv1.FeatureGates Stack lokiv1.LokiStackSpec ResourceRequirements internal.ComponentResources @@ -36,6 +34,8 @@ type Options struct { OpenShiftOptions openshift.Options Tenants Tenants + + TLSProfile TLSProfileSpec } // Tenants contains the configuration per tenant and secrets for authn/authz. @@ -88,3 +88,19 @@ type RulerSecret struct { // BearerToken contains the token used for bearer authentication. BearerToken string } + +// TLSProfileSpec is the desired behavior of a TLSProfileType. +type TLSProfileSpec struct { + // Ciphers is used to specify the cipher algorithms that are negotiated + // during the TLS handshake. + Ciphers []string + // MinTLSVersion is used to specify the minimal version of the TLS protocol + // that is negotiated during the TLS handshake. + MinTLSVersion string +} + +// TLSCipherSuites transforms TLSProfileSpec.Ciphers from a slice +// to a string of elements joined with a comma. +func (o Options) TLSCipherSuites() string { + return strings.Join(o.TLSProfile.Ciphers, ",") +} diff --git a/operator/internal/manifests/querier.go b/operator/internal/manifests/querier.go index d212588cd3..a0a3cc98c7 100644 --- a/operator/internal/manifests/querier.go +++ b/operator/internal/manifests/querier.go @@ -4,11 +4,11 @@ import ( "fmt" "path" - "github.com/ViaQ/logerr/v2/kverrors" "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/storage" - "github.com/imdario/mergo" + "github.com/ViaQ/logerr/v2/kverrors" + "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,7 +22,7 @@ import ( func BuildQuerier(opts Options) ([]client.Object, error) { deployment := NewQuerierDeployment(opts) if opts.Gates.HTTPEncryption { - if err := configureQuerierHTTPServicePKI(deployment, opts.Name); err != nil { + if err := configureQuerierHTTPServicePKI(deployment, opts); err != nil { return nil, err } } @@ -32,7 +32,7 @@ func BuildQuerier(opts Options) ([]client.Object, error) { } if opts.Gates.GRPCEncryption { - if err := configureQuerierGRPCServicePKI(deployment, opts.Name, opts.Namespace); err != nil { + if err := configureQuerierGRPCServicePKI(deployment, opts); err != nil { return nil, err } } @@ -109,6 +109,13 @@ func NewQuerierDeployment(opts Options) *appsv1.Deployment { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.Querier != nil { podSpec.Tolerations = opts.Stack.Template.Querier.Tolerations podSpec.NodeSelector = opts.Stack.Template.Querier.NodeSelector @@ -205,13 +212,13 @@ func NewQuerierHTTPService(opts Options) *corev1.Service { } } -func configureQuerierHTTPServicePKI(deployment *appsv1.Deployment, stackName string) error { - serviceName := serviceNameQuerierHTTP(stackName) +func configureQuerierHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNameQuerierHTTP(opts.Name) return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) } -func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, stackName, stackNS string) error { - caBundleName := signingCABundleName(stackName) +func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error { + caBundleName := signingCABundleName(opts.Name) secretVolumeSpec := corev1.PodSpec{ Volumes: []corev1.Volume{ { @@ -238,16 +245,22 @@ func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, stackName, st Args: []string{ // Enable GRPC over TLS for ingester client "-ingester.client.tls-enabled=true", + fmt.Sprintf("-ingester.client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-ingester.client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-ingester.client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(stackName), stackNS)), + fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(opts.Name), opts.Namespace)), // Enable GRPC over TLS for query frontend client "-querier.frontend-client.tls-enabled=true", + fmt.Sprintf("-querier.frontend-client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-querier.frontend-client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-querier.frontend-client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-querier.frontend-client.tls-server-name=%s", fqdn(serviceNameQueryFrontendGRPC(stackName), stackNS)), + fmt.Sprintf("-querier.frontend-client.tls-server-name=%s", fqdn(serviceNameQueryFrontendGRPC(opts.Name), opts.Namespace)), // Enable GRPC over TLS for boltb-shipper index-gateway client "-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(stackName), stackNS)), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(opts.Name), opts.Namespace)), }, } @@ -259,6 +272,6 @@ func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, stackName, st return kverrors.Wrap(err, "failed to merge container") } - serviceName := serviceNameQuerierGRPC(stackName) + serviceName := serviceNameQuerierGRPC(opts.Name) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) } diff --git a/operator/internal/manifests/query-frontend.go b/operator/internal/manifests/query-frontend.go index 80cd974289..4785db7b77 100644 --- a/operator/internal/manifests/query-frontend.go +++ b/operator/internal/manifests/query-frontend.go @@ -4,8 +4,9 @@ import ( "fmt" "path" - "github.com/ViaQ/logerr/v2/kverrors" "github.com/grafana/loki/operator/internal/manifests/internal/config" + + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -20,13 +21,13 @@ import ( func BuildQueryFrontend(opts Options) ([]client.Object, error) { deployment := NewQueryFrontendDeployment(opts) if opts.Gates.HTTPEncryption { - if err := configureQueryFrontendHTTPServicePKI(deployment, opts.Name); err != nil { + if err := configureQueryFrontendHTTPServicePKI(deployment, opts); err != nil { return nil, err } } if opts.Gates.GRPCEncryption { - if err := configureQueryFrontendGRPCServicePKI(deployment, opts.Name); err != nil { + if err := configureQueryFrontendGRPCServicePKI(deployment, opts); err != nil { return nil, err } } @@ -115,6 +116,13 @@ func NewQueryFrontendDeployment(opts Options) *appsv1.Deployment { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.QueryFrontend != nil { podSpec.Tolerations = opts.Stack.Template.QueryFrontend.Tolerations podSpec.NodeSelector = opts.Stack.Template.QueryFrontend.NodeSelector @@ -211,24 +219,34 @@ func NewQueryFrontendHTTPService(opts Options) *corev1.Service { } } -func configureQueryFrontendHTTPServicePKI(deployment *appsv1.Deployment, stackName string) error { - serviceName := serviceNameQueryFrontendHTTP(stackName) - caBundleName := signingCABundleName(stackName) +func configureQueryFrontendHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNameQueryFrontendHTTP(opts.Name) + caBundleName := signingCABundleName(opts.Name) - if err := configureTailCA(deployment, lokiFrontendContainerName, caBundleName, caBundleDir, caFile); err != nil { + err := configureTailCA( + deployment, + lokiFrontendContainerName, + caBundleName, + caBundleDir, + caFile, + opts.TLSProfile.MinTLSVersion, + opts.TLSCipherSuites(), + ) + if err != nil { return err } + return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) } -func configureQueryFrontendGRPCServicePKI(deployment *appsv1.Deployment, stackName string) error { - serviceName := serviceNameQueryFrontendGRPC(stackName) +func configureQueryFrontendGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNameQueryFrontendGRPC(opts.Name) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) } // ConfigureQueryFrontendDeployment configures CA certificate when TLS is enabled. func configureTailCA(d *appsv1.Deployment, - qfContainerName, caBundleVolumeName, caDir, caFile string, + qfContainerName, caBundleVolumeName, caDir, caFile, minTLSVersion, cipherSuites string, ) error { var qfIdx int for i, c := range d.Spec.Template.Spec.Containers { @@ -240,6 +258,8 @@ func configureTailCA(d *appsv1.Deployment, containerSpec := corev1.Container{ Args: []string{ + fmt.Sprintf("-frontend.tail-tls-config.tls-cipher-suites=%s", cipherSuites), + fmt.Sprintf("-frontend.tail-tls-config.tls-min-version=%s", minTLSVersion), fmt.Sprintf("-frontend.tail-tls-config.tls-ca-path=%s/%s", caDir, caFile), }, VolumeMounts: []corev1.VolumeMount{ diff --git a/operator/internal/manifests/query-frontend_test.go b/operator/internal/manifests/query-frontend_test.go index 92ded54dd0..fdb811e4e5 100644 --- a/operator/internal/manifests/query-frontend_test.go +++ b/operator/internal/manifests/query-frontend_test.go @@ -7,6 +7,7 @@ import ( lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests/internal/config" + "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -62,6 +63,10 @@ func TestConfigureQueryFrontendHTTPServicePKI(t *testing.T) { }, }, }, + TLSProfile: TLSProfileSpec{ + MinTLSVersion: "TLSVersion1.2", + Ciphers: []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}, + }, } d := appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ @@ -119,6 +124,8 @@ func TestConfigureQueryFrontendHTTPServicePKI(t *testing.T) { Name: lokiFrontendContainerName, Args: []string{ "-target=query-frontend", + "-frontend.tail-tls-config.tls-cipher-suites=TLS_RSA_WITH_AES_128_CBC_SHA", + "-frontend.tail-tls-config.tls-min-version=TLSVersion1.2", fmt.Sprintf("-frontend.tail-tls-config.tls-ca-path=%s/%s", caBundleDir, caFile), fmt.Sprintf("-server.http-tls-cert-path=%s", path.Join(httpTLSDir, tlsCertFile)), fmt.Sprintf("-server.http-tls-key-path=%s", path.Join(httpTLSDir, tlsKeyFile)), @@ -193,7 +200,7 @@ func TestConfigureQueryFrontendHTTPServicePKI(t *testing.T) { }, } - err := configureQueryFrontendHTTPServicePKI(&d, opts.Name) + err := configureQueryFrontendHTTPServicePKI(&d, opts) require.Nil(t, err) require.Equal(t, expected, d) } diff --git a/operator/internal/manifests/ruler.go b/operator/internal/manifests/ruler.go index bdf7be06df..e8537279f5 100644 --- a/operator/internal/manifests/ruler.go +++ b/operator/internal/manifests/ruler.go @@ -4,10 +4,11 @@ import ( "fmt" "path" - "github.com/ViaQ/logerr/v2/kverrors" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/openshift" + + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -23,13 +24,13 @@ import ( func BuildRuler(opts Options) ([]client.Object, error) { statefulSet := NewRulerStatefulSet(opts) if opts.Gates.HTTPEncryption { - if err := configureRulerHTTPServicePKI(statefulSet, opts.Name); err != nil { + if err := configureRulerHTTPServicePKI(statefulSet, opts); err != nil { return nil, err } } if opts.Gates.GRPCEncryption { - if err := configureRulerGRPCServicePKI(statefulSet, opts.Name, opts.Namespace); err != nil { + if err := configureRulerGRPCServicePKI(statefulSet, opts); err != nil { return nil, err } } @@ -143,6 +144,13 @@ func NewRulerStatefulSet(opts Options) *appsv1.StatefulSet { SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), } + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, + fmt.Sprintf("-server.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-server.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), + ) + } + if opts.Stack.Template != nil && opts.Stack.Template.Ruler != nil { podSpec.Tolerations = opts.Stack.Template.Ruler.Tolerations podSpec.NodeSelector = opts.Stack.Template.Ruler.NodeSelector @@ -318,13 +326,13 @@ func NewRulerHTTPService(opts Options) *corev1.Service { } } -func configureRulerHTTPServicePKI(statefulSet *appsv1.StatefulSet, stackName string) error { - serviceName := serviceNameRulerHTTP(stackName) +func configureRulerHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error { + serviceName := serviceNameRulerHTTP(opts.Name) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) } -func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNs string) error { - caBundleName := signingCABundleName(stackName) +func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error { + caBundleName := signingCABundleName(opts.Name) secretVolumeSpec := corev1.PodSpec{ Volumes: []corev1.Volume{ { @@ -351,16 +359,22 @@ func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNs st Args: []string{ // Enable GRPC over TLS for ruler client "-ruler.client.tls-enabled=true", + fmt.Sprintf("-ruler.client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-ruler.client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-ruler.client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-ruler.client.tls-server-name=%s", fqdn(serviceNameRulerGRPC(stackName), stackNs)), + fmt.Sprintf("-ruler.client.tls-server-name=%s", fqdn(serviceNameRulerGRPC(opts.Name), opts.Namespace)), // Enable GRPC over TLS for ingester client "-ingester.client.tls-enabled=true", + fmt.Sprintf("-ingester.client.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-ingester.client.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-ingester.client.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(stackName), stackNs)), + fmt.Sprintf("-ingester.client.tls-server-name=%s", fqdn(serviceNameIngesterGRPC(opts.Name), opts.Namespace)), // Enable GRPC over TLS for boltb-shipper index-gateway client "-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-cipher-suites=%s", opts.TLSCipherSuites()), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-min-version=%s", opts.TLSProfile.MinTLSVersion), fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-ca-path=%s", signingCAPath()), - fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(stackName), stackNs)), + fmt.Sprintf("-boltdb.shipper.index-gateway-client.grpc.tls-server-name=%s", fqdn(serviceNameIndexGatewayGRPC(opts.Name), opts.Namespace)), }, } @@ -372,7 +386,7 @@ func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNs st return kverrors.Wrap(err, "failed to merge container") } - serviceName := serviceNameRulerGRPC(stackName) + serviceName := serviceNameRulerGRPC(opts.Name) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) }