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
- [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

@ -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

@ -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
}

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

@ -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")

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

@ -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)
}

@ -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)

@ -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)
}

@ -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)
}

@ -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 {

@ -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
}

@ -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{

@ -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)
}

@ -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)
}

@ -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 {

@ -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"},
},

@ -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)
}

@ -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)
}

@ -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, ",")
}

@ -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)
}

@ -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{

@ -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)
}

@ -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)
}

Loading…
Cancel
Save