Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

operator: add mTLS authentication to tenants #9906

Merged
merged 15 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions operator/apis/loki/v1/lokistack_types.go
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ type OIDCSpec struct {
UsernameClaim string `json:"usernameClaim,omitempty"`
}

// MTLSSpec specifies mTLS configuration parameters.
type MTLSSpec struct {
// CASpec defines the spec for the custom CA for tenant's authentication.
//
// +required
// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="CA ConfigMap"
CASpec *CASpec `json:"caSpec"`
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
}

// AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component.
type AuthenticationSpec struct {
// TenantName defines the name of the tenant.
Expand All @@ -199,10 +209,15 @@ type AuthenticationSpec struct {
TenantID string `json:"tenantId"`
// OIDC defines the spec for the OIDC tenant's authentication.
//
// +required
// +kubebuilder:validation:Required
// +optional
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="OIDC Configuration"
OIDC *OIDCSpec `json:"oidc"`
OIDC *OIDCSpec `json:"oidc,omitempty"`

// TLSConfig defines the spec for the mTLS tenant's authentication.
//
// +optional
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="mTLS Configuration"
MTLS *MTLSSpec `json:"mTLS,omitempty"`
}

// ModeType is the authentication/authorization mode in which LokiStack Gateway will be configured.
Expand Down Expand Up @@ -414,8 +429,7 @@ type HashRingSpec struct {
MemberList *MemberListSpec `json:"memberlist,omitempty"`
}

// ObjectStorageTLSSpec is the TLS configuration for reaching the object storage endpoint.
type ObjectStorageTLSSpec struct {
type CASpec struct {
// Key is the data key of a ConfigMap containing a CA certificate.
// It needs to be in the same namespace as the LokiStack custom resource.
// If empty, it defaults to "service-ca.crt".
Expand All @@ -433,6 +447,11 @@ type ObjectStorageTLSSpec struct {
CA string `json:"caName"`
}

// ObjectStorageTLSSpec is the TLS configuration for reaching the object storage endpoint.
type ObjectStorageTLSSpec struct {
CASpec `json:",inline"`
}

// ObjectStorageSecretType defines the type of storage which can be used with the Loki cluster.
//
// +kubebuilder:validation:Enum=azure;gcs;s3;swift;alibabacloud;
Expand Down Expand Up @@ -926,8 +945,15 @@ const (
// ReasonMissingGatewayTenantSecret when the required tenant secret
// for authentication is missing.
ReasonMissingGatewayTenantSecret LokiStackConditionReason = "MissingGatewayTenantSecret"
// ReasonMissingGatewayTenantConfigMap when the required tenant configmap
// for authentication is missing.
ReasonMissingGatewayTenantConfigMap LokiStackConditionReason = "MissingGatewayTenantConfigMap"
// ReasonInvalidGatewayTenantSecret when the format of the secret is invalid.
ReasonInvalidGatewayTenantSecret LokiStackConditionReason = "InvalidGatewayTenantSecret"
// ReasonInvalidGatewayTenantConfigMap when the format of the configmap is invalid.
ReasonInvalidGatewayTenantConfigMap LokiStackConditionReason = "InvalidGatewayTenantConfigMap"
// ReasonMissingGatewayAuthenticationConfig when the config for when a tenant is missing authentication config
ReasonMissingGatewayAuthenticationConfig LokiStackConditionReason = "MissingGatewayTenantAuthenticationConfig"
// ReasonInvalidTenantsConfiguration when the tenant configuration provided is invalid.
ReasonInvalidTenantsConfiguration LokiStackConditionReason = "InvalidTenantsConfiguration"
// ReasonMissingGatewayOpenShiftBaseDomain when the reconciler cannot lookup the OpenShift DNS base domain.
Expand Down
41 changes: 41 additions & 0 deletions operator/apis/loki/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion operator/apis/loki/v1beta1/lokistack_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,9 @@ func (src *LokiStack) ConvertTo(dstRaw conversion.Hub) error {
var storageTLS *v1.ObjectStorageTLSSpec
if src.Spec.Storage.TLS != nil {
storageTLS = &v1.ObjectStorageTLSSpec{
CA: src.Spec.Storage.TLS.CA,
CASpec: v1.CASpec{
CA: src.Spec.Storage.TLS.CA,
},
}
}

Expand Down
1 change: 0 additions & 1 deletion operator/controllers/loki/lokistack_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ type LokiStackReconciler struct {
// +kubebuilder:rbac:groups=loki.grafana.com,resources=lokistacks/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=loki.grafana.com,resources=lokistacks/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=pods;nodes;services;endpoints;configmaps;secrets;serviceaccounts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
periklis marked this conversation as resolved.
Show resolved Hide resolved
// +kubebuilder:rbac:groups=apps,resources=deployments;statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings;clusterroles;roles;rolebindings,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors;prometheusrules,verbs=get;list;watch;create;update;delete
Expand Down
88 changes: 70 additions & 18 deletions operator/internal/handlers/internal/gateway/tenant_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,39 +30,83 @@ func GetTenantSecrets(
var (
tenantSecrets []*manifests.TenantSecrets
gatewaySecret corev1.Secret
caConfigMap corev1.ConfigMap
)

for _, tenant := range stack.Spec.Tenants.Authentication {
key := client.ObjectKey{Name: tenant.OIDC.Secret.Name, Namespace: req.Namespace}
if err := k.Get(ctx, key, &gatewaySecret); err != nil {
if apierrors.IsNotFound(err) {
switch {
case tenant.OIDC != nil:
key := client.ObjectKey{Name: tenant.OIDC.Secret.Name, Namespace: req.Namespace}
if err := k.Get(ctx, key, &gatewaySecret); err != nil {
if apierrors.IsNotFound(err) {
return nil, &status.DegradedError{
Message: fmt.Sprintf("Missing secrets for tenant %s", tenant.TenantName),
Reason: lokiv1.ReasonMissingGatewayTenantSecret,
Requeue: true,
}
}
return nil, kverrors.Wrap(err, "failed to lookup lokistack gateway tenant secret",
"name", key)
}

oidcSecret, err := extractOIDCSecret(&gatewaySecret)
if err != nil {
return nil, &status.DegradedError{
Message: fmt.Sprintf("Missing secrets for tenant %s", tenant.TenantName),
Reason: lokiv1.ReasonMissingGatewayTenantSecret,
Message: "Invalid gateway tenant secret contents",
Reason: lokiv1.ReasonInvalidGatewayTenantSecret,
Requeue: true,
}
}
return nil, kverrors.Wrap(err, "failed to lookup lokistack gateway tenant secret",
"name", key)
}

var ts *manifests.TenantSecrets
ts, err := extractSecret(&gatewaySecret, tenant.TenantName)
if err != nil {
tenantSecrets = append(tenantSecrets, &manifests.TenantSecrets{
TenantName: tenant.TenantName,
OIDCSecret: oidcSecret,
})
case tenant.MTLS != nil:
key := client.ObjectKey{Name: tenant.MTLS.CASpec.CA, Namespace: req.Namespace}
if err := k.Get(ctx, key, &caConfigMap); err != nil {
if apierrors.IsNotFound(err) {
return nil, &status.DegradedError{
Message: fmt.Sprintf("Missing configmap for tenant %s", tenant.TenantName),
Reason: lokiv1.ReasonMissingGatewayTenantConfigMap,
Requeue: true,
}
}
return nil, kverrors.Wrap(err, "failed to lookup lokistack gateway tenant configMap",
"name", key)
}
// Default key if the user doesn't specify it
cmKey := "service-ca.crt"
if tenant.MTLS.CASpec.CAKey != "" {
cmKey = tenant.MTLS.CASpec.CAKey
}
err := checkKeyIsPresent(&caConfigMap, cmKey)
if err != nil {
return nil, &status.DegradedError{
Message: "Invalid gateway tenant configmap contents",
Reason: lokiv1.ReasonInvalidGatewayTenantConfigMap,
Requeue: true,
}
}
tenantSecrets = append(tenantSecrets, &manifests.TenantSecrets{
TenantName: tenant.TenantName,
MTLSSecret: &manifests.MTLSSecret{
CAPath: manifests.TenantMTLSCAPath(tenant.TenantName, cmKey),
},
})
default:
return nil, &status.DegradedError{
Message: "Invalid gateway tenant secret contents",
Message: "No gateway tenant authentication method provided",
Reason: lokiv1.ReasonInvalidGatewayTenantSecret,
Requeue: true,
}
}
tenantSecrets = append(tenantSecrets, ts)
}

return tenantSecrets, nil
}

// extractSecret reads a k8s secret into a manifest tenant secret struct if valid.
func extractSecret(s *corev1.Secret, tenantName string) (*manifests.TenantSecrets, error) {
// extractOIDCSecret reads a k8s secret into a manifest tenant secret struct if valid.
func extractOIDCSecret(s *corev1.Secret) (*manifests.OIDCSecret, error) {
// Extract and validate mandatory fields
clientID := s.Data["clientID"]
if len(clientID) == 0 {
Expand All @@ -71,10 +115,18 @@ func extractSecret(s *corev1.Secret, tenantName string) (*manifests.TenantSecret
clientSecret := s.Data["clientSecret"]
issuerCAPath := s.Data["issuerCAPath"]

return &manifests.TenantSecrets{
TenantName: tenantName,
return &manifests.OIDCSecret{
ClientID: string(clientID),
ClientSecret: string(clientSecret),
IssuerCAPath: string(issuerCAPath),
}, nil
}

// checkKeyIsPresent checks if key is present in the configmap
func checkKeyIsPresent(cm *corev1.ConfigMap, key string) error {
ca := cm.Data[key]
if len(ca) == 0 {
return kverrors.New(fmt.Sprintf("missing %s field", key), "field", key)
}
return nil
}
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ func TestGetTenantSecrets_StaticMode(t *testing.T) {

expected := []*manifests.TenantSecrets{
{
TenantName: "test",
ClientID: "test",
ClientSecret: "test",
IssuerCAPath: "/path/to/ca/file",
TenantName: "test",
OIDCSecret: &manifests.OIDCSecret{
ClientID: "test",
ClientSecret: "test",
IssuerCAPath: "/path/to/ca/file",
},
},
}
require.ElementsMatch(t, ts, expected)
Expand Down Expand Up @@ -134,10 +136,12 @@ func TestGetTenantSecrets_DynamicMode(t *testing.T) {

expected := []*manifests.TenantSecrets{
{
TenantName: "test",
ClientID: "test",
ClientSecret: "test",
IssuerCAPath: "/path/to/ca/file",
TenantName: "test",
OIDCSecret: &manifests.OIDCSecret{
ClientID: "test",
ClientSecret: "test",
IssuerCAPath: "/path/to/ca/file",
},
},
}
require.ElementsMatch(t, ts, expected)
Expand Down Expand Up @@ -174,7 +178,7 @@ func TestExtractSecret(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()

_, err := extractSecret(tst.secret, tst.tenantName)
_, err := extractOIDCSecret(tst.secret)
if !tst.wantErr {
require.NoError(t, err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,9 @@ func TestCreateOrUpdateLokiStack_WhenMissingCAConfigMap_SetDegraded(t *testing.T
Type: lokiv1.ObjectStorageSecretS3,
},
TLS: &lokiv1.ObjectStorageTLSSpec{
CA: "not-existing",
lokiv1.CASpec{
CA: "not-existing",
},
},
},
},
Expand Down Expand Up @@ -1014,7 +1016,9 @@ func TestCreateOrUpdateLokiStack_WhenInvalidCAConfigMap_SetDegraded(t *testing.T
Type: lokiv1.ObjectStorageSecretS3,
},
TLS: &lokiv1.ObjectStorageTLSSpec{
CA: invalidCAConfigMap.Name,
lokiv1.CASpec{
CA: invalidCAConfigMap.Name,
},
},
},
},
Expand Down
26 changes: 19 additions & 7 deletions operator/internal/manifests/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func BuildGateway(opts Options) ([]client.Object, error) {

if opts.Stack.Tenants != nil {
mode := opts.Stack.Tenants.Mode
if err := configureGatewayDeploymentForMode(dpl, mode, opts.Gates, minTLSVersion, ciphers); err != nil {
if err := configureGatewayDeploymentForMode(dpl, mode, opts.Gates, minTLSVersion, ciphers, opts.Stack.Tenants); err != nil {
return nil, err
}

Expand Down Expand Up @@ -438,13 +438,25 @@ func gatewayConfigObjs(opt Options) (*corev1.ConfigMap, *corev1.Secret, string,

// gatewayConfigOptions converts Options to gateway.Options
func gatewayConfigOptions(opt Options) gateway.Options {
var gatewaySecrets []*gateway.Secret
var (
gatewaySecrets []*gateway.Secret
gatewaySecret *gateway.Secret
)
for _, secret := range opt.Tenants.Secrets {
gatewaySecret := &gateway.Secret{
TenantName: secret.TenantName,
ClientID: secret.ClientID,
ClientSecret: secret.ClientSecret,
IssuerCAPath: secret.IssuerCAPath,
gatewaySecret = &gateway.Secret{
TenantName: secret.TenantName,
}
switch {
case secret.OIDCSecret != nil:
gatewaySecret.OIDC = &gateway.OIDC{
ClientID: secret.OIDCSecret.ClientID,
ClientSecret: secret.OIDCSecret.ClientSecret,
IssuerCAPath: secret.OIDCSecret.IssuerCAPath,
}
case secret.MTLSSecret != nil:
gatewaySecret.MTLS = &gateway.MTLS{
CAPath: secret.MTLSSecret.CAPath,
}
}
gatewaySecrets = append(gatewaySecrets, gatewaySecret)
}
Expand Down
Loading