mirror of https://github.com/grafana/loki
operator: Configure gateway to honor the global TLS security profile (#6870)
parent
53e01a795a
commit
a068c1241d
@ -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) |
||||
}) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue