operator: Add TLS profile support for Loki server and client HTTP and GRPC TLS options (#7322)

pull/7371/head
Gerard Vanloo 3 years ago committed by GitHub
parent 7979cfbe07
commit bc7f2f5adb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      operator/CHANGELOG.md
  2. 17
      operator/apis/config/v1/projectconfig_types.go
  3. 20
      operator/apis/config/v1/zz_generated.deepcopy.go
  4. 1
      operator/bundle/manifests/loki-operator-manager-config_v1_configmap.yaml
  5. 38
      operator/cmd/loki-broker/main.go
  6. 1
      operator/config/overlays/openshift/controller_manager_config.yaml
  7. 5
      operator/controllers/loki/lokistack_controller.go
  8. 96
      operator/controllers/loki/lokistack_controller_test.go
  9. 78
      operator/internal/handlers/internal/tlsprofile/tlsprofile.go
  10. 141
      operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go
  11. 25
      operator/internal/handlers/lokistack_create_or_update.go
  12. 34
      operator/internal/manifests/build.go
  13. 111
      operator/internal/manifests/build_test.go
  14. 21
      operator/internal/manifests/compactor.go
  15. 28
      operator/internal/manifests/distributor.go
  16. 4
      operator/internal/manifests/gateway.go
  17. 6
      operator/internal/manifests/gateway_test.go
  18. 20
      operator/internal/manifests/indexgateway.go
  19. 32
      operator/internal/manifests/ingester.go
  20. 24
      operator/internal/manifests/options.go
  21. 37
      operator/internal/manifests/querier.go
  22. 40
      operator/internal/manifests/query-frontend.go
  23. 9
      operator/internal/manifests/query-frontend_test.go
  24. 36
      operator/internal/manifests/ruler.go

@ -1,5 +1,6 @@
## Main ## 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 - [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 - [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 - [6951](https://github.com/grafana/loki/pull/6951) **Red-GV**: Adding operational Lokistack alerts

@ -21,6 +21,10 @@ type OpenShiftFeatureGates struct {
// ExtendedRuleValidation enables extended validation of AlertingRule and RecordingRule // ExtendedRuleValidation enables extended validation of AlertingRule and RecordingRule
// to enforce tenancy in an OpenShift context. // to enforce tenancy in an OpenShift context.
ExtendedRuleValidation bool `json:"ruleExtendedValidation,omitempty"` 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. // 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 contains a set of feature gates supported only on OpenShift.
OpenShift OpenShiftFeatureGates `json:"openshift,omitempty"` 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"` TLSProfile string `json:"tlsProfile,omitempty"`
} }
@ -98,16 +103,6 @@ const (
TLSProfileModernType TLSProfileType = "Modern" 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 //+kubebuilder:object:root=true
// ProjectConfig is the Schema for the projectconfigs API // ProjectConfig is the Schema for the projectconfigs API

@ -65,23 +65,3 @@ func (in *ProjectConfig) DeepCopyObject() runtime.Object {
} }
return nil 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
}

@ -46,6 +46,7 @@ data:
servingCertsService: true servingCertsService: true
gatewayRoute: true gatewayRoute: true
ruleExtendedValidation: true ruleExtendedValidation: true
clusterTLSPolicy: true
kind: ConfigMap kind: ConfigMap
metadata: metadata:
labels: labels:

@ -7,13 +7,14 @@ import (
"path" "path"
"strings" "strings"
"github.com/ViaQ/logerr/v2/log"
"github.com/go-logr/logr"
configv1 "github.com/grafana/loki/operator/apis/config/v1" 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" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests" "github.com/grafana/loki/operator/internal/manifests"
"github.com/grafana/loki/operator/internal/manifests/storage" "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" "sigs.k8s.io/yaml"
) )
@ -129,22 +130,21 @@ func main() {
} }
if cfg.featureFlags.TLSProfile != "" && if cfg.featureFlags.TLSProfile != "" &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileOldType) && cfg.featureFlags.TLSProfile != string(configv1.TLSProfileOldType) &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileIntermediateType) && cfg.featureFlags.TLSProfile != string(configv1.TLSProfileIntermediateType) &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileModernType) { cfg.featureFlags.TLSProfile != string(configv1.TLSProfileModernType) {
logger.Error(err, "failed to parse TLS profile. Allowed values: 'Old', 'Intermediate', 'Modern'", "value", cfg.featureFlags.TLSProfile) logger.Error(err, "failed to parse TLS profile. Allowed values: 'Old', 'Intermediate', 'Modern'", "value", cfg.featureFlags.TLSProfile)
os.Exit(1) os.Exit(1)
} }
// Convert config to manifest.Options // Convert config to manifest.Options
opts := manifests.Options{ opts := manifests.Options{
Name: cfg.Name, Name: cfg.Name,
Namespace: cfg.Namespace, Namespace: cfg.Namespace,
Image: cfg.Image, Image: cfg.Image,
Stack: ls.Spec, Stack: ls.Spec,
Gates: cfg.featureFlags, Gates: cfg.featureFlags,
ObjectStorage: cfg.objectStorage, ObjectStorage: cfg.objectStorage,
TLSProfileType: projectconfigv1.TLSProfileType(cfg.featureFlags.TLSProfile),
} }
if optErr := manifests.ApplyDefaultSettings(&opts); optErr != nil { if optErr := manifests.ApplyDefaultSettings(&opts); optErr != nil {
@ -152,6 +152,18 @@ func main() {
os.Exit(1) 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) objects, err := manifests.BuildAll(opts)
if err != nil { if err != nil {
logger.Error(err, "failed to build manifests") logger.Error(err, "failed to build manifests")

@ -43,3 +43,4 @@ featureGates:
servingCertsService: true servingCertsService: true
gatewayRoute: true gatewayRoute: true
ruleExtendedValidation: true ruleExtendedValidation: true
clusterTLSPolicy: true

@ -15,6 +15,7 @@ import (
"github.com/grafana/loki/operator/internal/status" "github.com/grafana/loki/operator/internal/status"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"
openshiftconfigv1 "github.com/openshift/api/config/v1"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/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) bld = bld.Owns(&networkingv1.Ingress{}, updateOrDeleteOnlyPred)
} }
if r.FeatureGates.OpenShift.ClusterTLSPolicy {
bld = bld.Owns(&openshiftconfigv1.APIServer{}, updateOrDeleteOnlyPred)
}
return bld.Complete(r) return bld.Complete(r)
} }

@ -12,6 +12,7 @@ import (
"github.com/ViaQ/logerr/v2/log" "github.com/ViaQ/logerr/v2/log"
"github.com/go-logr/logr" "github.com/go-logr/logr"
openshiftconfigv1 "github.com/openshift/api/config/v1"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
@ -74,63 +75,74 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test
// Require owned resources // Require owned resources
type test struct { type test struct {
obj client.Object obj client.Object
index int index int
featureGates configv1.FeatureGates ownCallsCount int
pred builder.OwnsOption featureGates configv1.FeatureGates
pred builder.OwnsOption
} }
table := []test{ table := []test{
{ {
obj: &corev1.ConfigMap{}, obj: &corev1.ConfigMap{},
index: 0, index: 0,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &corev1.ServiceAccount{}, obj: &corev1.ServiceAccount{},
index: 1, index: 1,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &corev1.Service{}, obj: &corev1.Service{},
index: 2, index: 2,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &appsv1.Deployment{}, obj: &appsv1.Deployment{},
index: 3, index: 3,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &appsv1.StatefulSet{}, obj: &appsv1.StatefulSet{},
index: 4, index: 4,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &rbacv1.ClusterRole{}, obj: &rbacv1.ClusterRole{},
index: 5, index: 5,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &rbacv1.ClusterRoleBinding{}, obj: &rbacv1.ClusterRoleBinding{},
index: 6, index: 6,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &rbacv1.Role{}, obj: &rbacv1.Role{},
index: 7, index: 7,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &rbacv1.RoleBinding{}, obj: &rbacv1.RoleBinding{},
index: 8, index: 8,
pred: updateOrDeleteOnlyPred, ownCallsCount: 10,
pred: updateOrDeleteOnlyPred,
}, },
// The next two share the same index, because the // The next two share the same index, because the
// controller either reconciles an Ingress (i.e. Kubernetes) // controller either reconciles an Ingress (i.e. Kubernetes)
// or a Route (i.e. OpenShift). // or a Route (i.e. OpenShift).
{ {
obj: &networkingv1.Ingress{}, obj: &networkingv1.Ingress{},
index: 9, index: 9,
ownCallsCount: 10,
featureGates: configv1.FeatureGates{ featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{ OpenShift: configv1.OpenShiftFeatureGates{
GatewayRoute: false, GatewayRoute: false,
@ -139,8 +151,9 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test
pred: updateOrDeleteOnlyPred, pred: updateOrDeleteOnlyPred,
}, },
{ {
obj: &routev1.Route{}, obj: &routev1.Route{},
index: 9, index: 9,
ownCallsCount: 10,
featureGates: configv1.FeatureGates{ featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{ OpenShift: configv1.OpenShiftFeatureGates{
GatewayRoute: true, GatewayRoute: true,
@ -148,6 +161,17 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test
}, },
pred: updateOrDeleteOnlyPred, pred: updateOrDeleteOnlyPred,
}, },
{
obj: &openshiftconfigv1.APIServer{},
index: 10,
ownCallsCount: 11,
featureGates: configv1.FeatureGates{
OpenShift: configv1.OpenShiftFeatureGates{
ClusterTLSPolicy: true,
},
},
pred: updateOrDeleteOnlyPred,
},
} }
for _, tst := range table { for _, tst := range table {
b := &k8sfakes.FakeBuilder{} b := &k8sfakes.FakeBuilder{}
@ -159,7 +183,7 @@ func TestLokiStackController_RegisterOwnedResourcesForUpdateOrDeleteOnly(t *test
require.NoError(t, err) require.NoError(t, err)
// Require Owns-Calls for all owned resources // 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 // Require Owns-call options to have delete predicate only
obj, opts := b.OwnsArgsForCall(tst.index) obj, opts := b.OwnsArgsForCall(tst.index)

@ -3,69 +3,37 @@ package tlsprofile
import ( import (
"context" "context"
"github.com/go-logr/logr" configv1 "github.com/grafana/loki/operator/apis/config/v1"
projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/external/k8s" "github.com/grafana/loki/operator/internal/external/k8s"
openshiftv1 "github.com/openshift/api/config/v1" openshiftconfigv1 "github.com/openshift/api/config/v1"
"github.com/openshift/library-go/pkg/crypto"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
) )
// APIServerName is the apiserver resource name used to fetch it. // APIServerName is the apiserver resource name used to fetch it.
const APIServerName = "cluster" const APIServerName = "cluster"
// GetSecurityProfileInfo gets the tls profile info to apply. // GetTLSSecurityProfile gets the tls profile info to apply.
func GetSecurityProfileInfo(ctx context.Context, k k8s.Client, log logr.Logger, tlsProfileType projectconfigv1.TLSProfileType) (projectconfigv1.TLSProfileSpec, error) { func GetTLSSecurityProfile(ctx context.Context, k k8s.Client, tlsProfileType configv1.TLSProfileType) (*openshiftconfigv1.TLSSecurityProfile, error) {
var tlsProfile openshiftv1.TLSSecurityProfile switch tlsProfileType {
case configv1.TLSProfileOldType:
if tlsProfileType != "" { return &openshiftconfigv1.TLSSecurityProfile{
tlsProfile = openshiftv1.TLSSecurityProfile{ Type: openshiftconfigv1.TLSProfileOldType,
Type: openshiftv1.TLSProfileType(tlsProfileType), }, nil
} case configv1.TLSProfileIntermediateType:
} else { return &openshiftconfigv1.TLSSecurityProfile{
tlsProfile = openshiftv1.TLSSecurityProfile{ Type: openshiftconfigv1.TLSProfileIntermediateType,
Type: openshiftv1.TLSProfileIntermediateType, }, nil
} case configv1.TLSProfileModernType:
return &openshiftconfigv1.TLSSecurityProfile{
var apiServer openshiftv1.APIServer Type: openshiftconfigv1.TLSProfileModernType,
}, nil
default:
var apiServer openshiftconfigv1.APIServer
if err := k.Get(ctx, client.ObjectKey{Name: APIServerName}, &apiServer); err != nil { if err := k.Get(ctx, client.ObjectKey{Name: APIServerName}, &apiServer); err != nil {
log.Error(err, "failed to lookup apiServer. Using Intermediate profile") return nil, kverrors.Wrap(err, "failed to lookup openshift apiServer")
}
if apiServer.Spec.TLSSecurityProfile != nil {
tlsProfile = *apiServer.Spec.TLSSecurityProfile
} }
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)
} }

@ -4,11 +4,11 @@ import (
"context" "context"
"testing" "testing"
"github.com/go-logr/logr" configv1 "github.com/grafana/loki/operator/apis/config/v1"
projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes" "github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/handlers/internal/tlsprofile" "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" "github.com/stretchr/testify/assert"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -17,78 +17,43 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
) )
var ( func TestGetTLSSecurityProfile(t *testing.T) {
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) {
type tt struct { type tt struct {
desc string desc string
profile projectconfigv1.TLSProfileType profile configv1.TLSProfileType
expected projectconfigv1.TLSProfileSpec expected openshiftconfigv1.TLSSecurityProfile
} }
tc := []tt{ tc := []tt{
{ {
desc: "Old profile", desc: "Old profile",
profile: projectconfigv1.TLSProfileOldType, profile: configv1.TLSProfileOldType,
expected: projectconfigv1.TLSProfileSpec{ expected: openshiftconfigv1.TLSSecurityProfile{
MinTLSVersion: "VersionTLS10", Type: openshiftconfigv1.TLSProfileOldType,
Ciphers: ciphersOld,
}, },
}, },
{ {
desc: "Intermediate profile", desc: "Intermediate profile",
profile: projectconfigv1.TLSProfileIntermediateType, profile: configv1.TLSProfileIntermediateType,
expected: projectconfigv1.TLSProfileSpec{ expected: openshiftconfigv1.TLSSecurityProfile{
MinTLSVersion: "VersionTLS12", Type: openshiftconfigv1.TLSProfileIntermediateType,
Ciphers: ciphersIntermediate,
}, },
}, },
{ {
desc: "Modern profile", desc: "Modern profile",
profile: projectconfigv1.TLSProfileModernType, profile: configv1.TLSProfileModernType,
expected: projectconfigv1.TLSProfileSpec{ expected: openshiftconfigv1.TLSSecurityProfile{
MinTLSVersion: "VersionTLS13", Type: openshiftconfigv1.TLSProfileModernType,
// 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{},
}, },
}, },
} }
apiServer := openshiftconfigv1.APIServer{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
}
sw := &k8sfakes.FakeStatusWriter{} sw := &k8sfakes.FakeStatusWriter{}
k := &k8sfakes.FakeClient{} k := &k8sfakes.FakeClient{}
@ -106,10 +71,68 @@ func TestGetSecurityProfileInfo(t *testing.T) {
tc := tc tc := tc
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
t.Parallel() 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.Nil(t, err)
assert.NotNil(t, info) assert.NotNil(t, profile)
assert.EqualValues(t, tc.expected, info) 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)
}

@ -7,7 +7,6 @@ import (
"time" "time"
configv1 "github.com/grafana/loki/operator/apis/config/v1" 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" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1"
"github.com/grafana/loki/operator/internal/external/k8s" "github.com/grafana/loki/operator/internal/external/k8s"
@ -175,12 +174,12 @@ func CreateOrUpdateLokiStack(
if stack.Spec.Rules != nil && stack.Spec.Rules.Enabled { if stack.Spec.Rules != nil && stack.Spec.Rules.Enabled {
alertingRules, recordingRules, err = rules.List(ctx, k, req.Namespace, stack.Spec.Rules) alertingRules, recordingRules, err = rules.List(ctx, k, req.Namespace, stack.Spec.Rules)
if err != nil { 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) rulerConfig, err = rules.GetRulerConfig(ctx, k, req)
if err != nil { 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 { if rulerConfig != nil && rulerConfig.RemoteWriteSpec != nil && rulerConfig.RemoteWriteSpec.ClientSpec != nil {
@ -235,7 +234,6 @@ func CreateOrUpdateLokiStack(
Secrets: tenantSecrets, Secrets: tenantSecrets,
Configs: tenantConfigs, Configs: tenantConfigs,
}, },
TLSProfileType: projectconfigv1.TLSProfileType(fg.TLSProfile),
OpenShiftOptions: manifests_openshift.Options{ OpenShiftOptions: manifests_openshift.Options{
BuildOpts: manifests_openshift.BuildOptions{ BuildOpts: manifests_openshift.BuildOptions{
AlertManagerEnabled: ocpAmEnabled, AlertManagerEnabled: ocpAmEnabled,
@ -252,18 +250,27 @@ func CreateOrUpdateLokiStack(
if fg.LokiStackGateway { if fg.LokiStackGateway {
if optErr := manifests.ApplyGatewayDefaultOptions(&opts); optErr != nil { 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 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 { if err != nil {
ll.Error(err, "failed to get security profile info") // The API server is not guaranteed to be there nor have a result.
return err 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) objects, err := manifests.BuildAll(opts)
if err != nil { if err != nil {

@ -1,11 +1,13 @@
package manifests package manifests
import ( import (
"github.com/ViaQ/logerr/v2/kverrors"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/internal" "github.com/grafana/loki/operator/internal/manifests/internal"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
@ -137,3 +139,33 @@ func ApplyDefaultSettings(opts *Options) error {
return nil 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
}

@ -2,8 +2,10 @@ package manifests
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
openshiftconfigv1 "github.com/openshift/api/config/v1"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@ -11,6 +13,7 @@ import (
configv1 "github.com/grafana/loki/operator/apis/config/v1" configv1 "github.com/grafana/loki/operator/apis/config/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/internal" "github.com/grafana/loki/operator/internal/manifests/internal"
"github.com/stretchr/testify/require" "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) { func TestBuildAll_WithFeatureGates_ServiceMonitors(t *testing.T) {
type test struct { type test struct {
desc string desc string
@ -242,9 +327,19 @@ func TestBuildAll_WithFeatureGates_HTTPEncryption(t *testing.T) {
HTTPEncryption: true, 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) err := ApplyDefaultSettings(&opts)
require.NoError(t, err) require.NoError(t, err)
err = ApplyTLSSettings(&opts, nil)
require.NoError(t, err)
objects, buildErr := BuildAll(opts) objects, buildErr := BuildAll(opts)
require.NoError(t, buildErr) require.NoError(t, buildErr)
@ -295,6 +390,8 @@ func TestBuildAll_WithFeatureGates_HTTPEncryption(t *testing.T) {
} }
require.Contains(t, vms, expVolumeMount) 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-cert-path=/var/run/tls/http/tls.crt")
require.Contains(t, args, "-server.http-tls-key-path=/var/run/tls/http/tls.key") require.Contains(t, args, "-server.http-tls-key-path=/var/run/tls/http/tls.key")
require.Equal(t, corev1.URISchemeHTTPS, rps) require.Equal(t, corev1.URISchemeHTTPS, rps)
@ -484,6 +581,15 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) {
"test-ruler": "test-ruler-grpc", "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 { for _, tst := range table {
tst := tst tst := tst
t.Run(tst.desc, func(t *testing.T) { t.Run(tst.desc, func(t *testing.T) {
@ -492,6 +598,9 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) {
err := ApplyDefaultSettings(&tst.BuildOptions) err := ApplyDefaultSettings(&tst.BuildOptions)
require.NoError(t, err) require.NoError(t, err)
err = ApplyTLSSettings(&tst.BuildOptions, nil)
require.NoError(t, err)
objs, err := BuildAll(tst.BuildOptions) objs, err := BuildAll(tst.BuildOptions)
require.NoError(t, err) require.NoError(t, err)
@ -516,6 +625,8 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) {
args := []string{ args := []string{
"-server.grpc-tls-cert-path=/var/run/tls/grpc/tls.crt", "-server.grpc-tls-cert-path=/var/run/tls/grpc/tls.crt",
"-server.grpc-tls-key-path=/var/run/tls/grpc/tls.key", "-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{ vm := corev1.VolumeMount{

@ -6,6 +6,7 @@ import (
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/grafana/loki/operator/internal/manifests/storage" "github.com/grafana/loki/operator/internal/manifests/storage"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
@ -13,7 +14,6 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
) )
@ -21,7 +21,7 @@ import (
func BuildCompactor(opts Options) ([]client.Object, error) { func BuildCompactor(opts Options) ([]client.Object, error) {
statefulSet := NewCompactorStatefulSet(opts) statefulSet := NewCompactorStatefulSet(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureCompactorHTTPServicePKI(statefulSet, opts.Name); err != nil { if err := configureCompactorHTTPServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -31,7 +31,7 @@ func BuildCompactor(opts Options) ([]client.Object, error) {
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureCompactorGRPCServicePKI(statefulSet, opts.Name); err != nil { if err := configureCompactorGRPCServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -108,6 +108,13 @@ func NewCompactorStatefulSet(opts Options) *appsv1.StatefulSet {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.Compactor != nil {
podSpec.Tolerations = opts.Stack.Template.Compactor.Tolerations podSpec.Tolerations = opts.Stack.Template.Compactor.Tolerations
podSpec.NodeSelector = opts.Stack.Template.Compactor.NodeSelector 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 { func configureCompactorHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameCompactorHTTP(stackName) serviceName := serviceNameCompactorHTTP(opts.Name)
return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName)
} }
func configureCompactorGRPCServicePKI(sts *appsv1.StatefulSet, stackName string) error { func configureCompactorGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameCompactorGRPC(stackName) serviceName := serviceNameCompactorGRPC(opts.Name)
return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName)
} }

@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo" "github.com/imdario/mergo"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -20,13 +21,13 @@ import (
func BuildDistributor(opts Options) ([]client.Object, error) { func BuildDistributor(opts Options) ([]client.Object, error) {
deployment := NewDistributorDeployment(opts) deployment := NewDistributorDeployment(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureDistributorHTTPServicePKI(deployment, opts.Name); err != nil { if err := configureDistributorHTTPServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureDistributorGRPCServicePKI(deployment, opts.Name, opts.Namespace); err != nil { if err := configureDistributorGRPCServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -103,6 +104,13 @@ func NewDistributorDeployment(opts Options) *appsv1.Deployment {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.Distributor != nil {
podSpec.Tolerations = opts.Stack.Template.Distributor.Tolerations podSpec.Tolerations = opts.Stack.Template.Distributor.Tolerations
podSpec.NodeSelector = opts.Stack.Template.Distributor.NodeSelector 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 { func configureDistributorHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error {
serviceName := serviceNameDistributorHTTP(stackName) serviceName := serviceNameDistributorHTTP(opts.Name)
return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }
func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, stackName, stackNS string) error { func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error {
caBundleName := signingCABundleName(stackName) caBundleName := signingCABundleName(opts.Name)
secretVolumeSpec := corev1.PodSpec{ secretVolumeSpec := corev1.PodSpec{
Volumes: []corev1.Volume{ Volumes: []corev1.Volume{
{ {
@ -232,8 +240,10 @@ func configureDistributorGRPCServicePKI(deployment *appsv1.Deployment, stackName
Args: []string{ Args: []string{
// Enable GRPC over TLS for ingester client // Enable GRPC over TLS for ingester client
"-ingester.client.tls-enabled=true", "-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-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") return kverrors.Wrap(err, "failed to merge container")
} }
serviceName := serviceNameDistributorGRPC(stackName) serviceName := serviceNameDistributorGRPC(opts.Name)
return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }

@ -42,8 +42,8 @@ func BuildGateway(opts Options) ([]client.Object, error) {
objs := []client.Object{cm, dpl, svc, ing} objs := []client.Object{cm, dpl, svc, ing}
minTLSVersion := opts.TLSProfileSpec.MinTLSVersion minTLSVersion := opts.TLSProfile.MinTLSVersion
ciphersList := opts.TLSProfileSpec.Ciphers ciphersList := opts.TLSProfile.Ciphers
ciphers := strings.Join(ciphersList, `,`) ciphers := strings.Join(ciphersList, `,`)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {

@ -296,7 +296,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) {
HTTPEncryption: true, HTTPEncryption: true,
TLSProfile: string(configv1.TLSProfileOldType), TLSProfile: string(configv1.TLSProfileOldType),
}, },
TLSProfileSpec: configv1.TLSProfileSpec{ TLSProfile: TLSProfileSpec{
MinTLSVersion: "min-version", MinTLSVersion: "min-version",
Ciphers: []string{"cipher1", "cipher2"}, Ciphers: []string{"cipher1", "cipher2"},
}, },
@ -348,7 +348,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) {
HTTPEncryption: true, HTTPEncryption: true,
TLSProfile: string(configv1.TLSProfileOldType), TLSProfile: string(configv1.TLSProfileOldType),
}, },
TLSProfileSpec: configv1.TLSProfileSpec{ TLSProfile: TLSProfileSpec{
MinTLSVersion: "min-version", MinTLSVersion: "min-version",
Ciphers: []string{"cipher1", "cipher2"}, Ciphers: []string{"cipher1", "cipher2"},
}, },
@ -378,7 +378,7 @@ func TestBuildGateway_WithTLSProfile(t *testing.T) {
HTTPEncryption: true, HTTPEncryption: true,
TLSProfile: string(configv1.TLSProfileOldType), TLSProfile: string(configv1.TLSProfileOldType),
}, },
TLSProfileSpec: configv1.TLSProfileSpec{ TLSProfile: TLSProfileSpec{
MinTLSVersion: "min-version", MinTLSVersion: "min-version",
Ciphers: []string{"cipher1", "cipher2"}, Ciphers: []string{"cipher1", "cipher2"},
}, },

@ -6,6 +6,7 @@ import (
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/grafana/loki/operator/internal/manifests/storage" "github.com/grafana/loki/operator/internal/manifests/storage"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
@ -20,7 +21,7 @@ import (
func BuildIndexGateway(opts Options) ([]client.Object, error) { func BuildIndexGateway(opts Options) ([]client.Object, error) {
statefulSet := NewIndexGatewayStatefulSet(opts) statefulSet := NewIndexGatewayStatefulSet(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureIndexGatewayHTTPServicePKI(statefulSet, opts.Name); err != nil { if err := configureIndexGatewayHTTPServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -30,7 +31,7 @@ func BuildIndexGateway(opts Options) ([]client.Object, error) {
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureIndexGatewayGRPCServicePKI(statefulSet, opts.Name); err != nil { if err := configureIndexGatewayGRPCServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -107,6 +108,13 @@ func NewIndexGatewayStatefulSet(opts Options) *appsv1.StatefulSet {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.IndexGateway != nil {
podSpec.Tolerations = opts.Stack.Template.IndexGateway.Tolerations podSpec.Tolerations = opts.Stack.Template.IndexGateway.Tolerations
podSpec.NodeSelector = opts.Stack.Template.IndexGateway.NodeSelector 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 { func configureIndexGatewayHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameIndexGatewayHTTP(stackName) serviceName := serviceNameIndexGatewayHTTP(opts.Name)
return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName)
} }
func configureIndexGatewayGRPCServicePKI(sts *appsv1.StatefulSet, stackName string) error { func configureIndexGatewayGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameIndexGatewayGRPC(stackName) serviceName := serviceNameIndexGatewayGRPC(opts.Name)
return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName)
} }

@ -4,12 +4,11 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/grafana/loki/operator/internal/manifests/storage" "github.com/grafana/loki/operator/internal/manifests/storage"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo" "github.com/imdario/mergo"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
@ -24,7 +23,7 @@ import (
func BuildIngester(opts Options) ([]client.Object, error) { func BuildIngester(opts Options) ([]client.Object, error) {
statefulSet := NewIngesterStatefulSet(opts) statefulSet := NewIngesterStatefulSet(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureIngesterHTTPServicePKI(statefulSet, opts.Name); err != nil { if err := configureIngesterHTTPServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -34,7 +33,7 @@ func BuildIngester(opts Options) ([]client.Object, error) {
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureIngesterGRPCServicePKI(statefulSet, opts.Name, opts.Namespace); err != nil { if err := configureIngesterGRPCServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -121,6 +120,13 @@ func NewIngesterStatefulSet(opts Options) *appsv1.StatefulSet {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.Ingester != nil {
podSpec.Tolerations = opts.Stack.Template.Ingester.Tolerations podSpec.Tolerations = opts.Stack.Template.Ingester.Tolerations
podSpec.NodeSelector = opts.Stack.Template.Ingester.NodeSelector 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 { func configureIngesterHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameIngesterHTTP(stackName) serviceName := serviceNameIngesterHTTP(opts.Name)
return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName)
} }
func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNS string) error { func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error {
caBundleName := signingCABundleName(stackName) caBundleName := signingCABundleName(opts.Name)
secretVolumeSpec := corev1.PodSpec{ secretVolumeSpec := corev1.PodSpec{
Volumes: []corev1.Volume{ Volumes: []corev1.Volume{
{ {
@ -288,12 +294,16 @@ func configureIngesterGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNS
Args: []string{ Args: []string{
// Enable GRPC over TLS for ingester client // Enable GRPC over TLS for ingester client
"-ingester.client.tls-enabled=true", "-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-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 // Enable GRPC over TLS for boltb-shipper index-gateway client
"-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", "-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-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") return kverrors.Wrap(err, "failed to merge container")
} }
serviceName := serviceNameIngesterGRPC(stackName) serviceName := serviceNameIngesterGRPC(opts.Name)
return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName)
} }

@ -1,8 +1,9 @@
package manifests package manifests
import ( import (
"strings"
configv1 "github.com/grafana/loki/operator/apis/config/v1" 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" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1" lokiv1beta1 "github.com/grafana/loki/operator/apis/loki/v1beta1"
"github.com/grafana/loki/operator/internal/manifests/internal" "github.com/grafana/loki/operator/internal/manifests/internal"
@ -20,9 +21,6 @@ type Options struct {
GatewayBaseDomain string GatewayBaseDomain string
ConfigSHA1 string ConfigSHA1 string
TLSProfileType projectconfigv1.TLSProfileType
TLSProfileSpec projectconfigv1.TLSProfileSpec
Gates configv1.FeatureGates Gates configv1.FeatureGates
Stack lokiv1.LokiStackSpec Stack lokiv1.LokiStackSpec
ResourceRequirements internal.ComponentResources ResourceRequirements internal.ComponentResources
@ -36,6 +34,8 @@ type Options struct {
OpenShiftOptions openshift.Options OpenShiftOptions openshift.Options
Tenants Tenants Tenants Tenants
TLSProfile TLSProfileSpec
} }
// Tenants contains the configuration per tenant and secrets for authn/authz. // 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 contains the token used for bearer authentication.
BearerToken string 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, ",")
}

@ -4,11 +4,11 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/grafana/loki/operator/internal/manifests/storage" "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" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -22,7 +22,7 @@ import (
func BuildQuerier(opts Options) ([]client.Object, error) { func BuildQuerier(opts Options) ([]client.Object, error) {
deployment := NewQuerierDeployment(opts) deployment := NewQuerierDeployment(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureQuerierHTTPServicePKI(deployment, opts.Name); err != nil { if err := configureQuerierHTTPServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -32,7 +32,7 @@ func BuildQuerier(opts Options) ([]client.Object, error) {
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureQuerierGRPCServicePKI(deployment, opts.Name, opts.Namespace); err != nil { if err := configureQuerierGRPCServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -109,6 +109,13 @@ func NewQuerierDeployment(opts Options) *appsv1.Deployment {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.Querier != nil {
podSpec.Tolerations = opts.Stack.Template.Querier.Tolerations podSpec.Tolerations = opts.Stack.Template.Querier.Tolerations
podSpec.NodeSelector = opts.Stack.Template.Querier.NodeSelector 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 { func configureQuerierHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error {
serviceName := serviceNameQuerierHTTP(stackName) serviceName := serviceNameQuerierHTTP(opts.Name)
return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }
func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, stackName, stackNS string) error { func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error {
caBundleName := signingCABundleName(stackName) caBundleName := signingCABundleName(opts.Name)
secretVolumeSpec := corev1.PodSpec{ secretVolumeSpec := corev1.PodSpec{
Volumes: []corev1.Volume{ Volumes: []corev1.Volume{
{ {
@ -238,16 +245,22 @@ func configureQuerierGRPCServicePKI(deployment *appsv1.Deployment, stackName, st
Args: []string{ Args: []string{
// Enable GRPC over TLS for ingester client // Enable GRPC over TLS for ingester client
"-ingester.client.tls-enabled=true", "-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-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 // Enable GRPC over TLS for query frontend client
"-querier.frontend-client.tls-enabled=true", "-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-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 // Enable GRPC over TLS for boltb-shipper index-gateway client
"-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", "-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-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") return kverrors.Wrap(err, "failed to merge container")
} }
serviceName := serviceNameQuerierGRPC(stackName) serviceName := serviceNameQuerierGRPC(opts.Name)
return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }

@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/internal/config"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo" "github.com/imdario/mergo"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -20,13 +21,13 @@ import (
func BuildQueryFrontend(opts Options) ([]client.Object, error) { func BuildQueryFrontend(opts Options) ([]client.Object, error) {
deployment := NewQueryFrontendDeployment(opts) deployment := NewQueryFrontendDeployment(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureQueryFrontendHTTPServicePKI(deployment, opts.Name); err != nil { if err := configureQueryFrontendHTTPServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureQueryFrontendGRPCServicePKI(deployment, opts.Name); err != nil { if err := configureQueryFrontendGRPCServicePKI(deployment, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -115,6 +116,13 @@ func NewQueryFrontendDeployment(opts Options) *appsv1.Deployment {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.QueryFrontend != nil {
podSpec.Tolerations = opts.Stack.Template.QueryFrontend.Tolerations podSpec.Tolerations = opts.Stack.Template.QueryFrontend.Tolerations
podSpec.NodeSelector = opts.Stack.Template.QueryFrontend.NodeSelector 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 { func configureQueryFrontendHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error {
serviceName := serviceNameQueryFrontendHTTP(stackName) serviceName := serviceNameQueryFrontendHTTP(opts.Name)
caBundleName := signingCABundleName(stackName) 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 err
} }
return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }
func configureQueryFrontendGRPCServicePKI(deployment *appsv1.Deployment, stackName string) error { func configureQueryFrontendGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error {
serviceName := serviceNameQueryFrontendGRPC(stackName) serviceName := serviceNameQueryFrontendGRPC(opts.Name)
return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName)
} }
// ConfigureQueryFrontendDeployment configures CA certificate when TLS is enabled. // ConfigureQueryFrontendDeployment configures CA certificate when TLS is enabled.
func configureTailCA(d *appsv1.Deployment, func configureTailCA(d *appsv1.Deployment,
qfContainerName, caBundleVolumeName, caDir, caFile string, qfContainerName, caBundleVolumeName, caDir, caFile, minTLSVersion, cipherSuites string,
) error { ) error {
var qfIdx int var qfIdx int
for i, c := range d.Spec.Template.Spec.Containers { for i, c := range d.Spec.Template.Spec.Containers {
@ -240,6 +258,8 @@ func configureTailCA(d *appsv1.Deployment,
containerSpec := corev1.Container{ containerSpec := corev1.Container{
Args: []string{ 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), fmt.Sprintf("-frontend.tail-tls-config.tls-ca-path=%s/%s", caDir, caFile),
}, },
VolumeMounts: []corev1.VolumeMount{ VolumeMounts: []corev1.VolumeMount{

@ -7,6 +7,7 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" 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/internal/config"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/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{ d := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@ -119,6 +124,8 @@ func TestConfigureQueryFrontendHTTPServicePKI(t *testing.T) {
Name: lokiFrontendContainerName, Name: lokiFrontendContainerName,
Args: []string{ Args: []string{
"-target=query-frontend", "-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("-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-cert-path=%s", path.Join(httpTLSDir, tlsCertFile)),
fmt.Sprintf("-server.http-tls-key-path=%s", path.Join(httpTLSDir, tlsKeyFile)), 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.Nil(t, err)
require.Equal(t, expected, d) require.Equal(t, expected, d)
} }

@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/ViaQ/logerr/v2/kverrors"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" 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/internal/config"
"github.com/grafana/loki/operator/internal/manifests/openshift" "github.com/grafana/loki/operator/internal/manifests/openshift"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo" "github.com/imdario/mergo"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -23,13 +24,13 @@ import (
func BuildRuler(opts Options) ([]client.Object, error) { func BuildRuler(opts Options) ([]client.Object, error) {
statefulSet := NewRulerStatefulSet(opts) statefulSet := NewRulerStatefulSet(opts)
if opts.Gates.HTTPEncryption { if opts.Gates.HTTPEncryption {
if err := configureRulerHTTPServicePKI(statefulSet, opts.Name); err != nil { if err := configureRulerHTTPServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
if opts.Gates.GRPCEncryption { if opts.Gates.GRPCEncryption {
if err := configureRulerGRPCServicePKI(statefulSet, opts.Name, opts.Namespace); err != nil { if err := configureRulerGRPCServicePKI(statefulSet, opts); err != nil {
return nil, err return nil, err
} }
} }
@ -143,6 +144,13 @@ func NewRulerStatefulSet(opts Options) *appsv1.StatefulSet {
SecurityContext: podSecurityContext(opts.Gates.RuntimeSeccompProfile), 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 { if opts.Stack.Template != nil && opts.Stack.Template.Ruler != nil {
podSpec.Tolerations = opts.Stack.Template.Ruler.Tolerations podSpec.Tolerations = opts.Stack.Template.Ruler.Tolerations
podSpec.NodeSelector = opts.Stack.Template.Ruler.NodeSelector 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 { func configureRulerHTTPServicePKI(statefulSet *appsv1.StatefulSet, opts Options) error {
serviceName := serviceNameRulerHTTP(stackName) serviceName := serviceNameRulerHTTP(opts.Name)
return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName) return configureHTTPServicePKI(&statefulSet.Spec.Template.Spec, serviceName)
} }
func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNs string) error { func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, opts Options) error {
caBundleName := signingCABundleName(stackName) caBundleName := signingCABundleName(opts.Name)
secretVolumeSpec := corev1.PodSpec{ secretVolumeSpec := corev1.PodSpec{
Volumes: []corev1.Volume{ Volumes: []corev1.Volume{
{ {
@ -351,16 +359,22 @@ func configureRulerGRPCServicePKI(sts *appsv1.StatefulSet, stackName, stackNs st
Args: []string{ Args: []string{
// Enable GRPC over TLS for ruler client // Enable GRPC over TLS for ruler client
"-ruler.client.tls-enabled=true", "-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-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 // Enable GRPC over TLS for ingester client
"-ingester.client.tls-enabled=true", "-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-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 // Enable GRPC over TLS for boltb-shipper index-gateway client
"-boltdb.shipper.index-gateway-client.grpc.tls-enabled=true", "-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-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") return kverrors.Wrap(err, "failed to merge container")
} }
serviceName := serviceNameRulerGRPC(stackName) serviceName := serviceNameRulerGRPC(opts.Name)
return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName) return configureGRPCServicePKI(&sts.Spec.Template.Spec, serviceName)
} }

Loading…
Cancel
Save