operator: Configure gateway to honor the global TLS security profile (#6870)

pull/7075/head
Mohamed-Amine Bouqsimi 3 years ago committed by GitHub
parent 53e01a795a
commit a068c1241d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      operator/CHANGELOG.md
  2. 29
      operator/apis/config/v1/projectconfig_types.go
  3. 20
      operator/apis/config/v1/zz_generated.deepcopy.go
  4. 1
      operator/bundle/manifests/loki-operator.clusterserviceversion.yaml
  5. 24
      operator/cmd/loki-broker/main.go
  6. 1
      operator/config/rbac/role.yaml
  7. 2
      operator/controllers/loki/lokistack_controller.go
  8. 4
      operator/go.mod
  9. 6
      operator/go.sum
  10. 74
      operator/internal/handlers/internal/tlsprofile/tlsprofile.go
  11. 114
      operator/internal/handlers/internal/tlsprofile/tlsprofile_test.go
  12. 11
      operator/internal/handlers/lokistack_create_or_update.go
  13. 14
      operator/internal/manifests/gateway.go
  14. 8
      operator/internal/manifests/gateway_tenants.go
  15. 23
      operator/internal/manifests/gateway_tenants_test.go
  16. 101
      operator/internal/manifests/gateway_test.go
  17. 13
      operator/internal/manifests/openshift/configure.go
  18. 5
      operator/internal/manifests/openshift/opa_openshift.go
  19. 4
      operator/internal/manifests/options.go

@ -1,5 +1,6 @@
## Main
- [6870](https://github.com/grafana/loki/pull/6870) **aminesnow**: Configure gateway to honor the global tlsSecurityProfile on Openshift
- [6999](https://github.com/grafana/loki/pull/6999) **Red-GV**: Adding LokiStack Gateway alerts
- [7000](https://github.com/grafana/loki/pull/7000) **xperimental**: Configure default node affinity for all pods
- [6923](https://github.com/grafana/loki/pull/6923) **xperimental**: Reconcile owner reference for existing objects

@ -73,6 +73,35 @@ 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 string `json:"tlsProfile,omitempty"`
}
// TLSProfileType is a TLS security profile based on the Mozilla definitions:
// https://wiki.mozilla.org/Security/Server_Side_TLS
type TLSProfileType string
const (
// TLSProfileOldType is a TLS security profile based on:
// https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility
TLSProfileOldType TLSProfileType = "Old"
// TLSProfileIntermediateType is a TLS security profile based on:
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
TLSProfileIntermediateType TLSProfileType = "Intermediate"
// TLSProfileModernType is a TLS security profile based on:
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
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

@ -65,3 +65,23 @@ 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
}

@ -972,6 +972,7 @@ spec:
- apiGroups:
- config.openshift.io
resources:
- apiservers
- dnses
verbs:
- get

@ -11,6 +11,7 @@ import (
"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"
@ -58,6 +59,8 @@ func (c *config) registerFlags(f *flag.FlagSet) {
// Input and output file/dir options
f.StringVar(&c.crFilepath, "custom-resource.path", "", "Path to a custom resource YAML file.")
f.StringVar(&c.writeToDir, "output.write-dir", "", "write each file to the specified directory.")
// TLS profile option
f.StringVar(&c.featureFlags.TLSProfile, "tls-profile", "", "The TLS security Profile configuration.")
}
func (c *config) validateFlags(log logr.Logger) {
@ -126,14 +129,23 @@ func main() {
os.Exit(1)
}
if cfg.featureFlags.TLSProfile != "" &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileOldType) &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.TLSProfileIntermediateType) &&
cfg.featureFlags.TLSProfile != string(projectconfigv1.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,
Name: cfg.Name,
Namespace: cfg.Namespace,
Image: cfg.Image,
Stack: ls.Spec,
Gates: cfg.featureFlags,
ObjectStorage: cfg.objectStorage,
TLSProfileType: projectconfigv1.TLSProfileType(cfg.featureFlags.TLSProfile),
}
if optErr := manifests.ApplyDefaultSettings(&opts); optErr != nil {

@ -54,6 +54,7 @@ rules:
- apiGroups:
- config.openshift.io
resources:
- apiservers
- dnses
verbs:
- get

@ -83,7 +83,7 @@ type LokiStackReconciler struct {
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;prometheusrules,verbs=get;list;watch;create;update;delete
// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;create;update
// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update
// +kubebuilder:rbac:groups=config.openshift.io,resources=dnses,verbs=get;list;watch
// +kubebuilder:rbac:groups=config.openshift.io,resources=dnses;apiservers,verbs=get;list;watch
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;delete
// Reconcile is part of the main kubernetes reconciliation loop which aims to

@ -25,6 +25,7 @@ require github.com/ViaQ/logerr/v2 v2.0.0
require (
github.com/google/go-cmp v0.5.7
github.com/grafana/loki v1.6.2-0.20220708124813-b92f113cb096
github.com/openshift/library-go v0.0.0-20220622115547-84d884f4c9f6
gopkg.in/yaml.v2 v2.4.0
)
@ -148,7 +149,7 @@ require (
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220222172238-00053529121e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
@ -165,6 +166,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 // indirect
k8s.io/apiextensions-apiserver v0.24.0 // indirect
k8s.io/apiserver v0.24.0 // indirect
k8s.io/component-base v0.24.0 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect

@ -1074,6 +1074,8 @@ github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xA
github.com/openshift/api v0.0.0-20220712151050-2647eb31dee7 h1:zjlaMHqzNrrm8bnltBnrKLwxALoLAH/8UAkBEESrEOg=
github.com/openshift/api v0.0.0-20220712151050-2647eb31dee7/go.mod h1:LEnw1IVscIxyDnltE3Wi7bQb/QzIM8BfPNKoGA1Qlxw=
github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE=
github.com/openshift/library-go v0.0.0-20220622115547-84d884f4c9f6 h1:lmfmsIGq62lmj17qrZh4Gbbb86WvJw6pLhCNwNjB2Yk=
github.com/openshift/library-go v0.0.0-20220622115547-84d884f4c9f6/go.mod h1:AMZwYwSdbvALDl3QobEzcJ2IeDO7DYLsr42izKzh524=
github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc=
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg=
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo=
@ -1586,8 +1588,9 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -2089,6 +2092,7 @@ k8s.io/apiserver v0.18.3/go.mod h1:tHQRmthRPLUtwqsOnJJMoI8SW3lnoReZeE861lH8vUw=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/apiserver v0.24.0 h1:GR7kGsjOMfilRvlG3Stxv/3uz/ryvJ/aZXc5pqdsNV0=
k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA=
k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY=
k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw=

@ -0,0 +1,74 @@
package tlsprofile
import (
"context"
"github.com/ViaQ/logerr/v2/kverrors"
projectconfigv1 "github.com/grafana/loki/operator/apis/config/v1"
"github.com/grafana/loki/operator/internal/external/k8s"
openshiftv1 "github.com/openshift/api/config/v1"
"github.com/openshift/library-go/pkg/crypto"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"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, 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
if err := k.Get(ctx, client.ObjectKey{Name: APIServerName}, &apiServer); err != nil {
if !apierrors.IsNotFound(err) {
return projectconfigv1.TLSProfileSpec{}, kverrors.Wrap(err, "failed to lookup apiServer")
}
}
if apiServer.Spec.TLSSecurityProfile != nil {
tlsProfile = *apiServer.Spec.TLSSecurityProfile
}
}
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)
}

@ -0,0 +1,114 @@
package tlsprofile_test
import (
"context"
"testing"
projectconfigv1 "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"
"github.com/stretchr/testify/assert"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"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) {
type tt struct {
desc string
profile projectconfigv1.TLSProfileType
expected projectconfigv1.TLSProfileSpec
}
tc := []tt{
{
desc: "Old profile",
profile: projectconfigv1.TLSProfileOldType,
expected: projectconfigv1.TLSProfileSpec{
MinTLSVersion: "VersionTLS10",
Ciphers: ciphersOld,
},
},
{
desc: "Intermediate profile",
profile: projectconfigv1.TLSProfileIntermediateType,
expected: projectconfigv1.TLSProfileSpec{
MinTLSVersion: "VersionTLS12",
Ciphers: ciphersIntermediate,
},
},
{
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{},
},
},
}
sw := &k8sfakes.FakeStatusWriter{}
k := &k8sfakes.FakeClient{}
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 }
for _, tc := range tc {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
info, err := tlsprofile.GetSecurityProfileInfo(context.TODO(), k, tc.profile)
assert.Nil(t, err)
assert.NotNil(t, info)
assert.EqualValues(t, tc.expected, info)
})
}
}

@ -7,12 +7,14 @@ 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"
"github.com/grafana/loki/operator/internal/handlers/internal/gateway"
"github.com/grafana/loki/operator/internal/handlers/internal/rules"
"github.com/grafana/loki/operator/internal/handlers/internal/storage"
"github.com/grafana/loki/operator/internal/handlers/internal/tlsprofile"
"github.com/grafana/loki/operator/internal/manifests"
storageoptions "github.com/grafana/loki/operator/internal/manifests/storage"
"github.com/grafana/loki/operator/internal/metrics"
@ -224,6 +226,7 @@ func CreateOrUpdateLokiStack(
Secrets: tenantSecrets,
Configs: tenantConfigs,
},
TLSProfileType: projectconfigv1.TLSProfileType(fg.TLSProfile),
}
ll.Info("begin building manifests")
@ -240,6 +243,14 @@ func CreateOrUpdateLokiStack(
}
}
spec, err := tlsprofile.GetSecurityProfileInfo(ctx, k, opts.TLSProfileType)
if err != nil {
ll.Error(err, "failed to get security profile info")
return err
}
opts.TLSProfileSpec = spec
objects, err := manifests.BuildAll(opts)
if err != nil {
ll.Error(err, "failed to build manifests")

@ -4,6 +4,7 @@ import (
"crypto/sha1"
"fmt"
"path"
"strings"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/imdario/mergo"
@ -41,16 +42,21 @@ func BuildGateway(opts Options) ([]client.Object, error) {
objs := []client.Object{cm, dpl, svc, ing}
minTLSVersion := opts.TLSProfileSpec.MinTLSVersion
ciphersList := opts.TLSProfileSpec.Ciphers
ciphers := strings.Join(ciphersList, `,`)
if opts.Gates.HTTPEncryption {
serviceName := serviceNameGatewayHTTP(opts.Name)
if err := configureGatewayMetricsPKI(&dpl.Spec.Template.Spec, serviceName); err != nil {
if err := configureGatewayMetricsPKI(&dpl.Spec.Template.Spec, serviceName, minTLSVersion, ciphers); err != nil {
return nil, err
}
}
if opts.Stack.Tenants != nil {
mode := opts.Stack.Tenants.Mode
if err := configureGatewayDeploymentForMode(dpl, mode, opts.Gates, opts.Name, opts.Namespace); err != nil {
if err := configureGatewayDeploymentForMode(dpl, mode, opts.Gates, opts.Name, opts.Namespace, minTLSVersion, ciphers); err != nil {
return nil, err
}
@ -343,7 +349,7 @@ func gatewayConfigOptions(opt Options) gateway.Options {
}
}
func configureGatewayMetricsPKI(podSpec *corev1.PodSpec, serviceName string) error {
func configureGatewayMetricsPKI(podSpec *corev1.PodSpec, serviceName, minTLSVersion, ciphers string) error {
var gwIndex int
for i, c := range podSpec.Containers {
if c.Name == gatewayContainerName {
@ -378,6 +384,8 @@ func configureGatewayMetricsPKI(podSpec *corev1.PodSpec, serviceName string) err
Args: []string{
fmt.Sprintf("--tls.internal.server.cert-file=%s", certFile),
fmt.Sprintf("--tls.internal.server.key-file=%s", keyFile),
fmt.Sprintf("--tls.min-version=%s", minTLSVersion),
fmt.Sprintf("--tls.cipher-suites=%s", ciphers),
},
}
uriSchemeContainerSpec := corev1.Container{

@ -55,7 +55,11 @@ func ApplyGatewayDefaultOptions(opts *Options) error {
return nil
}
func configureGatewayDeploymentForMode(d *appsv1.Deployment, mode lokiv1.ModeType, fg configv1.FeatureGates, stackName, stackNs string) error {
func configureGatewayDeploymentForMode(
d *appsv1.Deployment, mode lokiv1.ModeType,
fg configv1.FeatureGates, stackName, stackNs string,
minTLSVersion string, ciphers string,
) error {
switch mode {
case lokiv1.Static, lokiv1.Dynamic:
return nil // nothing to configure
@ -79,6 +83,8 @@ func configureGatewayDeploymentForMode(d *appsv1.Deployment, mode lokiv1.ModeTyp
secretName,
serverName,
gatewayHTTPPort,
minTLSVersion,
ciphers,
)
}

@ -258,11 +258,12 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--logs.write.endpoint=http://example.com",
fmt.Sprintf("--web.healthchecks.url=https://localhost:%d", gatewayHTTPPort),
"--tls.client-auth-type=NoClientCert",
"--tls.min-version=VersionTLS12",
"--tls.server.cert-file=/var/run/tls/http/tls.crt",
"--tls.server.key-file=/var/run/tls/http/tls.key",
"--tls.healthchecks.server-ca-file=/var/run/ca/service-ca.crt",
fmt.Sprintf("--tls.healthchecks.server-name=%s", "test-gateway-http.test-ns.svc.cluster.local"),
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
VolumeMounts: []corev1.VolumeMount{
{
@ -291,7 +292,6 @@ func TestConfigureDeploymentForMode(t *testing.T) {
Image: "quay.io/observatorium/opa-openshift:latest",
Args: []string{
"--log.level=warn",
"--tls.min-version=VersionTLS12",
"--opa.package=lokistack",
"--opa.matcher=kubernetes_namespace_name",
"--web.listen=:8082",
@ -378,6 +378,8 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--logs.tail.endpoint=http://example.com",
"--logs.write.endpoint=http://example.com",
fmt.Sprintf("--web.healthchecks.url=http://localhost:%d", gatewayHTTPPort),
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
VolumeMounts: []corev1.VolumeMount{
{
@ -431,8 +433,9 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--logs.tail.endpoint=http://example.com",
"--logs.write.endpoint=http://example.com",
fmt.Sprintf("--web.healthchecks.url=https://localhost:%d", gatewayHTTPPort),
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
"--tls.client-auth-type=NoClientCert",
"--tls.min-version=VersionTLS12",
"--tls.server.cert-file=/var/run/tls/http/tls.crt",
"--tls.server.key-file=/var/run/tls/http/tls.key",
"--tls.healthchecks.server-ca-file=/var/run/ca/service-ca.crt",
@ -465,7 +468,6 @@ func TestConfigureDeploymentForMode(t *testing.T) {
Image: "quay.io/observatorium/opa-openshift:latest",
Args: []string{
"--log.level=warn",
"--tls.min-version=VersionTLS12",
"--opa.package=lokistack",
"--opa.matcher=kubernetes_namespace_name",
"--web.listen=:8082",
@ -473,6 +475,8 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--web.healthchecks.url=http://localhost:8082",
"--tls.internal.server.cert-file=/var/run/tls/http/tls.crt",
"--tls.internal.server.key-file=/var/run/tls/http/tls.key",
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
`--openshift.mappings=application=loki.grafana.com`,
`--openshift.mappings=infrastructure=loki.grafana.com`,
`--openshift.mappings=audit=loki.grafana.com`,
@ -566,6 +570,8 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--logs.tail.endpoint=http://example.com",
"--logs.write.endpoint=http://example.com",
fmt.Sprintf("--web.healthchecks.url=http://localhost:%d", gatewayHTTPPort),
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
VolumeMounts: []corev1.VolumeMount{
{
@ -617,9 +623,10 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--logs.tail.endpoint=https://example.com",
"--logs.write.endpoint=https://example.com",
fmt.Sprintf("--web.healthchecks.url=https://localhost:%d", gatewayHTTPPort),
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
"--logs.tls.ca-file=/var/run/ca/service-ca.crt",
"--tls.client-auth-type=NoClientCert",
"--tls.min-version=VersionTLS12",
"--tls.server.cert-file=/var/run/tls/http/tls.crt",
"--tls.server.key-file=/var/run/tls/http/tls.key",
"--tls.healthchecks.server-ca-file=/var/run/ca/service-ca.crt",
@ -657,7 +664,6 @@ func TestConfigureDeploymentForMode(t *testing.T) {
Image: "quay.io/observatorium/opa-openshift:latest",
Args: []string{
"--log.level=warn",
"--tls.min-version=VersionTLS12",
"--opa.package=lokistack",
"--opa.matcher=kubernetes_namespace_name",
"--web.listen=:8082",
@ -665,6 +671,8 @@ func TestConfigureDeploymentForMode(t *testing.T) {
"--web.healthchecks.url=http://localhost:8082",
"--tls.internal.server.cert-file=/var/run/tls/http/tls.crt",
"--tls.internal.server.key-file=/var/run/tls/http/tls.key",
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
`--openshift.mappings=application=loki.grafana.com`,
`--openshift.mappings=infrastructure=loki.grafana.com`,
`--openshift.mappings=audit=loki.grafana.com`,
@ -736,11 +744,12 @@ func TestConfigureDeploymentForMode(t *testing.T) {
},
},
}
for _, tc := range tc {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
err := configureGatewayDeploymentForMode(tc.dpl, tc.mode, tc.featureGates, "test", "test-ns")
err := configureGatewayDeploymentForMode(tc.dpl, tc.mode, tc.featureGates, "test", "test-ns", "min-version", "cipher1,cipher2")
require.NoError(t, err)
require.Equal(t, tc.want, tc.dpl)
})

@ -279,3 +279,104 @@ func TestBuildGateway_WithExtraObjectsForTenantMode_ReplacesIngressWithRoute(t *
require.NotContains(t, kinds, "*v1.Ingress")
require.Contains(t, kinds, "*v1.Route")
}
func TestBuildGateway_WithTLSProfile(t *testing.T) {
type tt struct {
desc string
mode lokiv1.ModeType
authZ *lokiv1.AuthorizationSpec
expectedArgs []string
}
tc := []tt{
{
desc: "static mode",
mode: lokiv1.Static,
authZ: &lokiv1.AuthorizationSpec{
Roles: []lokiv1.RoleSpec{
{
Name: "some-name",
Resources: []string{"metrics"},
Tenants: []string{"test-a"},
Permissions: []lokiv1.PermissionType{"read"},
},
},
RoleBindings: []lokiv1.RoleBindingsSpec{
{
Name: "test-a",
Subjects: []lokiv1.Subject{
{
Name: "test@example.com",
Kind: "user",
},
},
Roles: []string{"read-write"},
},
},
},
expectedArgs: []string{
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
},
{
desc: "dynamic mode",
mode: lokiv1.Dynamic,
expectedArgs: []string{
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
},
{
desc: "openshift-logging mode",
mode: lokiv1.OpenshiftLogging,
expectedArgs: []string{
"--tls.min-version=min-version",
"--tls.cipher-suites=cipher1,cipher2",
},
},
}
options := Options{
Name: "abcd",
Namespace: "efgh",
Gates: configv1.FeatureGates{
LokiStackGateway: true,
HTTPEncryption: true,
TLSProfile: string(configv1.TLSProfileOldType),
},
TLSProfileSpec: configv1.TLSProfileSpec{
MinTLSVersion: "min-version",
Ciphers: []string{"cipher1", "cipher2"},
},
Stack: lokiv1.LokiStackSpec{
Template: &lokiv1.LokiTemplateSpec{
Gateway: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
},
Tenants: &lokiv1.TenantsSpec{},
},
}
for _, tc := range tc {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
options.Stack.Tenants.Mode = tc.mode
options.Stack.Tenants.Authorization = tc.authZ
objs, err := BuildGateway(options)
require.NoError(t, err)
d, ok := objs[1].(*appsv1.Deployment)
require.True(t, ok)
for _, c := range d.Spec.Template.Spec.Containers {
require.Contains(t, c.Args, tc.expectedArgs[0])
require.Contains(t, c.Args, tc.expectedArgs[1])
}
})
}
}

@ -46,6 +46,8 @@ func ConfigureGatewayDeployment(
withTLS, withCertSigningService bool,
secretName, serverName string,
gatewayHTTPPort int,
minTLSVersion string,
ciphers string,
) error {
var gwIndex int
for i, c := range d.Spec.Template.Spec.Containers {
@ -99,7 +101,6 @@ func ConfigureGatewayDeployment(
caFilePath := path.Join(caDir, caFile)
gwArgs = append(gwArgs,
"--tls.client-auth-type=NoClientCert",
"--tls.min-version=VersionTLS12",
fmt.Sprintf("--tls.server.cert-file=%s", certFilePath),
fmt.Sprintf("--tls.server.key-file=%s", keyFilePath),
fmt.Sprintf("--tls.healthchecks.server-ca-file=%s", caFilePath),
@ -107,7 +108,6 @@ func ConfigureGatewayDeployment(
gwContainer.ReadinessProbe.ProbeHandler.HTTPGet.Scheme = corev1.URISchemeHTTPS
gwContainer.LivenessProbe.ProbeHandler.HTTPGet.Scheme = corev1.URISchemeHTTPS
gwContainer.Args = gwArgs
// Create and mount TLS secrets volumes if not already created.
if !withTLS {
@ -125,13 +125,20 @@ func ConfigureGatewayDeployment(
ReadOnly: true,
MountPath: tlsDir,
})
// Add TLS profile info args since openshift gateway always uses TLS.
gwArgs = append(gwArgs,
fmt.Sprintf("--tls.min-version=%s", minTLSVersion),
fmt.Sprintf("--tls.cipher-suites=%s", ciphers))
}
gwContainer.Args = gwArgs
p := corev1.PodSpec{
ServiceAccountName: d.GetName(),
Containers: []corev1.Container{
*gwContainer,
newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile, withTLS),
newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile, minTLSVersion, ciphers, withTLS),
},
Volumes: gwVolumes,
}

@ -19,7 +19,7 @@ const (
opaDefaultLabelMatcher = "kubernetes_namespace_name"
)
func newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile string, withTLS bool) corev1.Container {
func newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile, minTLSVersion, ciphers string, withTLS bool) corev1.Container {
var (
image string
args []string
@ -35,7 +35,6 @@ func newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile string
uriScheme = corev1.URISchemeHTTP
args = []string{
"--log.level=warn",
"--tls.min-version=VersionTLS12",
fmt.Sprintf("--opa.package=%s", opaDefaultPackage),
fmt.Sprintf("--opa.matcher=%s", opaDefaultLabelMatcher),
fmt.Sprintf("--web.listen=:%d", GatewayOPAHTTPPort),
@ -50,6 +49,8 @@ func newOPAOpenShiftContainer(secretVolumeName, tlsDir, certFile, keyFile string
args = append(args, []string{
fmt.Sprintf("--tls.internal.server.cert-file=%s", certFilePath),
fmt.Sprintf("--tls.internal.server.key-file=%s", keyFilePath),
fmt.Sprintf("--tls.min-version=%s", minTLSVersion),
fmt.Sprintf("--tls.cipher-suites=%s", ciphers),
}...)
uriScheme = corev1.URISchemeHTTPS

@ -2,6 +2,7 @@ package manifests
import (
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"
@ -19,6 +20,9 @@ type Options struct {
GatewayBaseDomain string
ConfigSHA1 string
TLSProfileType projectconfigv1.TLSProfileType
TLSProfileSpec projectconfigv1.TLSProfileSpec
Gates configv1.FeatureGates
Stack lokiv1.LokiStackSpec
ResourceRequirements internal.ComponentResources

Loading…
Cancel
Save