From 50c4216d00cf5a0350bd8f389fd9d68c15708c46 Mon Sep 17 00:00:00 2001 From: Joao Marcal Date: Tue, 18 Jul 2023 18:57:29 +0200 Subject: [PATCH] operator: add mTLS authentication to tenants (#9906) --- operator/CHANGELOG.md | 1 + operator/apis/loki/v1/lokistack_types.go | 36 +- .../apis/loki/v1/zz_generated.deepcopy.go | 41 ++ operator/apis/loki/v1beta1/lokistack_types.go | 4 +- .../loki-operator.clusterserviceversion.yaml | 27 +- .../loki.grafana.com_lokistacks.yaml | 26 +- .../loki-operator.clusterserviceversion.yaml | 27 +- .../loki.grafana.com_lokistacks.yaml | 26 +- .../loki-operator.clusterserviceversion.yaml | 27 +- .../loki.grafana.com_lokistacks.yaml | 26 +- .../bases/loki.grafana.com_lokistacks.yaml | 26 +- .../loki-operator.clusterserviceversion.yaml | 17 + .../loki-operator.clusterserviceversion.yaml | 17 + .../loki-operator.clusterserviceversion.yaml | 17 + operator/config/rbac/role.yaml | 8 - .../controllers/loki/lokistack_controller.go | 1 - .../internal/gateway/tenant_secrets.go | 88 ++++- .../internal/gateway/tenant_secrets_test.go | 231 ++++++----- .../lokistack_create_or_update_test.go | 8 +- operator/internal/manifests/gateway.go | 29 +- .../internal/manifests/gateway_tenants.go | 89 ++++- .../manifests/gateway_tenants_test.go | 178 ++++++++- operator/internal/manifests/gateway_test.go | 10 +- .../manifests/internal/gateway/build.go | 4 +- .../manifests/internal/gateway/build_test.go | 359 +++++++++++++----- .../internal/gateway/gateway-tenants.yaml | 81 ++-- .../manifests/internal/gateway/options.go | 15 +- operator/internal/manifests/options.go | 13 +- operator/internal/manifests/var.go | 14 + 29 files changed, 1095 insertions(+), 351 deletions(-) diff --git a/operator/CHANGELOG.md b/operator/CHANGELOG.md index caf214ae2c618..de3787a56ba19 100644 --- a/operator/CHANGELOG.md +++ b/operator/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main +- [9906](https://github.com/grafana/loki/pull/9906) **JoaoBraveCoding**: Add mTLS authentication to tenants - [9963](https://github.com/grafana/loki/pull/9963) **xperimental**: Fix application tenant alertmanager configuration - [9795](https://github.com/grafana/loki/pull/9795) **JoaoBraveCoding**: Add initContainer to zone aware components to gatekeep them from starting without the AZ annotation - [9503](https://github.com/grafana/loki/pull/9503) **shwetaap**: Add Pod annotations with node topology labels to support zone aware scheduling diff --git a/operator/apis/loki/v1/lokistack_types.go b/operator/apis/loki/v1/lokistack_types.go index 9811e0f93af5c..298e668996d63 100644 --- a/operator/apis/loki/v1/lokistack_types.go +++ b/operator/apis/loki/v1/lokistack_types.go @@ -183,6 +183,16 @@ type OIDCSpec struct { UsernameClaim string `json:"usernameClaim,omitempty"` } +// MTLSSpec specifies mTLS configuration parameters. +type MTLSSpec struct { + // CA defines the spec for the custom CA for tenant's authentication. + // + // +required + // +kubebuilder:validation:Required + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="CA ConfigMap" + CA *CASpec `json:"ca"` +} + // AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component. type AuthenticationSpec struct { // TenantName defines the name of the tenant. @@ -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. @@ -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". @@ -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; @@ -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. diff --git a/operator/apis/loki/v1/zz_generated.deepcopy.go b/operator/apis/loki/v1/zz_generated.deepcopy.go index ced43973e2c21..41af4bf5ef3ee 100644 --- a/operator/apis/loki/v1/zz_generated.deepcopy.go +++ b/operator/apis/loki/v1/zz_generated.deepcopy.go @@ -380,6 +380,11 @@ func (in *AuthenticationSpec) DeepCopyInto(out *AuthenticationSpec) { *out = new(OIDCSpec) (*in).DeepCopyInto(*out) } + if in.MTLS != nil { + in, out := &in.MTLS, &out.MTLS + *out = new(MTLSSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticationSpec. @@ -426,6 +431,21 @@ func (in *AuthorizationSpec) DeepCopy() *AuthorizationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CASpec) DeepCopyInto(out *CASpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CASpec. +func (in *CASpec) DeepCopy() *CASpec { + if in == nil { + return nil + } + out := new(CASpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterProxy) DeepCopyInto(out *ClusterProxy) { *out = *in @@ -911,6 +931,26 @@ func (in *LokiTemplateSpec) DeepCopy() *LokiTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MTLSSpec) DeepCopyInto(out *MTLSSpec) { + *out = *in + if in.CA != nil { + in, out := &in.CA, &out.CA + *out = new(CASpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MTLSSpec. +func (in *MTLSSpec) DeepCopy() *MTLSSpec { + if in == nil { + return nil + } + out := new(MTLSSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MemberListSpec) DeepCopyInto(out *MemberListSpec) { *out = *in @@ -1020,6 +1060,7 @@ func (in *ObjectStorageSpec) DeepCopy() *ObjectStorageSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectStorageTLSSpec) DeepCopyInto(out *ObjectStorageTLSSpec) { *out = *in + out.CASpec = in.CASpec } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStorageTLSSpec. diff --git a/operator/apis/loki/v1beta1/lokistack_types.go b/operator/apis/loki/v1beta1/lokistack_types.go index 5a3b147ea2b21..28f59785280aa 100644 --- a/operator/apis/loki/v1beta1/lokistack_types.go +++ b/operator/apis/loki/v1beta1/lokistack_types.go @@ -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, + }, } } diff --git a/operator/bundle/community-openshift/manifests/loki-operator.clusterserviceversion.yaml b/operator/bundle/community-openshift/manifests/loki-operator.clusterserviceversion.yaml index e4aaa3911f026..d91aee8bf9e39 100644 --- a/operator/bundle/community-openshift/manifests/loki-operator.clusterserviceversion.yaml +++ b/operator/bundle/community-openshift/manifests/loki-operator.clusterserviceversion.yaml @@ -150,7 +150,7 @@ metadata: categories: OpenShift Optional, Logging & Tracing certified: "false" containerImage: docker.io/grafana/loki-operator:main-ac1c1fd - createdAt: "2023-07-04T17:17:17Z" + createdAt: "2023-07-17T16:04:46Z" description: The Community Loki Operator provides Kubernetes native deployment and management of Loki and related logging components. operators.operatorframework.io/builder: operator-sdk-unknown @@ -698,6 +698,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc @@ -1359,14 +1376,6 @@ spec: - get - list - watch - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - apiGroups: - apps resources: diff --git a/operator/bundle/community-openshift/manifests/loki.grafana.com_lokistacks.yaml b/operator/bundle/community-openshift/manifests/loki.grafana.com_lokistacks.yaml index 9debe361c0ee1..c194829794ab7 100644 --- a/operator/bundle/community-openshift/manifests/loki.grafana.com_lokistacks.yaml +++ b/operator/bundle/community-openshift/manifests/loki.grafana.com_lokistacks.yaml @@ -3659,6 +3659,31 @@ spec: description: AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component. properties: + mTLS: + description: TLSConfig defines the spec for the mTLS tenant's + authentication. + properties: + ca: + description: CA defines the spec for the custom CA for + tenant's authentication. + properties: + caKey: + description: 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". + type: string + caName: + description: CA is the name of a ConfigMap containing + a CA certificate. It needs to be in the same namespace + as the LokiStack custom resource. + type: string + required: + - caName + type: object + required: + - ca + type: object oidc: description: OIDC defines the spec for the OIDC tenant's authentication. @@ -3697,7 +3722,6 @@ spec: description: TenantName defines the name of the tenant. type: string required: - - oidc - tenantId - tenantName type: object diff --git a/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml b/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml index 9407609184ed1..92d0c0fa961f7 100644 --- a/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml +++ b/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml @@ -150,7 +150,7 @@ metadata: categories: OpenShift Optional, Logging & Tracing certified: "false" containerImage: docker.io/grafana/loki-operator:main-ac1c1fd - createdAt: "2023-07-04T17:17:12Z" + createdAt: "2023-07-17T16:04:44Z" description: The Community Loki Operator provides Kubernetes native deployment and management of Loki and related logging components. operators.operatorframework.io/builder: operator-sdk-unknown @@ -698,6 +698,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc @@ -1346,14 +1363,6 @@ spec: - get - list - watch - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - apiGroups: - apps resources: diff --git a/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml b/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml index fe851a38a93ca..c0c4097d53063 100644 --- a/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml +++ b/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml @@ -3659,6 +3659,31 @@ spec: description: AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component. properties: + mTLS: + description: TLSConfig defines the spec for the mTLS tenant's + authentication. + properties: + ca: + description: CA defines the spec for the custom CA for + tenant's authentication. + properties: + caKey: + description: 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". + type: string + caName: + description: CA is the name of a ConfigMap containing + a CA certificate. It needs to be in the same namespace + as the LokiStack custom resource. + type: string + required: + - caName + type: object + required: + - ca + type: object oidc: description: OIDC defines the spec for the OIDC tenant's authentication. @@ -3697,7 +3722,6 @@ spec: description: TenantName defines the name of the tenant. type: string required: - - oidc - tenantId - tenantName type: object diff --git a/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml b/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml index 451ad2714b0e1..f7ac804c93c96 100644 --- a/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml +++ b/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml @@ -150,7 +150,7 @@ metadata: categories: OpenShift Optional, Logging & Tracing certified: "false" containerImage: quay.io/openshift-logging/loki-operator:v0.1.0 - createdAt: "2023-07-04T17:17:21Z" + createdAt: "2023-07-17T16:04:47Z" description: | The Loki Operator for OCP provides a means for configuring and managing a Loki stack for cluster logging. ## Prerequisites and Requirements @@ -711,6 +711,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc @@ -1344,14 +1361,6 @@ spec: - get - list - watch - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - apiGroups: - apps resources: diff --git a/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml b/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml index 4267ea1ad3312..54a2d5527e2cb 100644 --- a/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml +++ b/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml @@ -3659,6 +3659,31 @@ spec: description: AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component. properties: + mTLS: + description: TLSConfig defines the spec for the mTLS tenant's + authentication. + properties: + ca: + description: CA defines the spec for the custom CA for + tenant's authentication. + properties: + caKey: + description: 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". + type: string + caName: + description: CA is the name of a ConfigMap containing + a CA certificate. It needs to be in the same namespace + as the LokiStack custom resource. + type: string + required: + - caName + type: object + required: + - ca + type: object oidc: description: OIDC defines the spec for the OIDC tenant's authentication. @@ -3697,7 +3722,6 @@ spec: description: TenantName defines the name of the tenant. type: string required: - - oidc - tenantId - tenantName type: object diff --git a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml index babe7ef7ac123..bb419cee885e3 100644 --- a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml +++ b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml @@ -3642,6 +3642,31 @@ spec: description: AuthenticationSpec defines the oidc configuration per tenant for lokiStack Gateway component. properties: + mTLS: + description: TLSConfig defines the spec for the mTLS tenant's + authentication. + properties: + ca: + description: CA defines the spec for the custom CA for + tenant's authentication. + properties: + caKey: + description: 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". + type: string + caName: + description: CA is the name of a ConfigMap containing + a CA certificate. It needs to be in the same namespace + as the LokiStack custom resource. + type: string + required: + - caName + type: object + required: + - ca + type: object oidc: description: OIDC defines the spec for the OIDC tenant's authentication. @@ -3680,7 +3705,6 @@ spec: description: TenantName defines the name of the tenant. type: string required: - - oidc - tenantId - tenantName type: object diff --git a/operator/config/manifests/community-openshift/bases/loki-operator.clusterserviceversion.yaml b/operator/config/manifests/community-openshift/bases/loki-operator.clusterserviceversion.yaml index 48aa49ee12b1c..a5a2d2f10a523 100644 --- a/operator/config/manifests/community-openshift/bases/loki-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/community-openshift/bases/loki-operator.clusterserviceversion.yaml @@ -611,6 +611,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc diff --git a/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml b/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml index 02ca02678a709..cd6cf3cb516ad 100644 --- a/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml @@ -611,6 +611,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc diff --git a/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml b/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml index bd788d64d3e63..68ecb43802a04 100644 --- a/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml @@ -623,6 +623,23 @@ spec: configuration spec per tenant. displayName: Authentication path: tenants.authentication + - description: TLSConfig defines the spec for the mTLS tenant's authentication. + displayName: mTLS Configuration + path: tenants.authentication[0].mTLS + - description: CA defines the spec for the custom CA for tenant's authentication. + displayName: CA ConfigMap + path: tenants.authentication[0].mTLS.ca + - description: 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". + displayName: CA ConfigMap Key + path: tenants.authentication[0].mTLS.ca.caKey + - description: CA is the name of a ConfigMap containing a CA certificate. It + needs to be in the same namespace as the LokiStack custom resource. + displayName: CA ConfigMap Name + path: tenants.authentication[0].mTLS.ca.caName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: OIDC defines the spec for the OIDC tenant's authentication. displayName: OIDC Configuration path: tenants.authentication[0].oidc diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml index 509c03861a4f6..f2df6c8a2cfe0 100644 --- a/operator/config/rbac/role.yaml +++ b/operator/config/rbac/role.yaml @@ -35,14 +35,6 @@ rules: - get - list - watch -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch - apiGroups: - apps resources: diff --git a/operator/controllers/loki/lokistack_controller.go b/operator/controllers/loki/lokistack_controller.go index f9f2a541c2f24..f085cac5a060f 100644 --- a/operator/controllers/loki/lokistack_controller.go +++ b/operator/controllers/loki/lokistack_controller.go @@ -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 // +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 diff --git a/operator/internal/handlers/internal/gateway/tenant_secrets.go b/operator/internal/handlers/internal/gateway/tenant_secrets.go index c1bad8b930b2f..8901d50caebb9 100644 --- a/operator/internal/handlers/internal/gateway/tenant_secrets.go +++ b/operator/internal/handlers/internal/gateway/tenant_secrets.go @@ -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.CA.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.CA.CAKey != "" { + cmKey = tenant.MTLS.CA.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 { @@ -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 +} diff --git a/operator/internal/handlers/internal/gateway/tenant_secrets_test.go b/operator/internal/handlers/internal/gateway/tenant_secrets_test.go index 354c301ca6913..3c5eaf0dc7728 100644 --- a/operator/internal/handlers/internal/gateway/tenant_secrets_test.go +++ b/operator/internal/handlers/internal/gateway/tenant_secrets_test.go @@ -2,6 +2,7 @@ package gateway import ( "context" + "strings" "testing" "github.com/stretchr/testify/require" @@ -17,24 +18,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func TestGetTenantSecrets_StaticMode(t *testing.T) { - k := &k8sfakes.FakeClient{} - r := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: "my-stack", - Namespace: "some-ns", - }, - } - - s := &lokiv1.LokiStack{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mystack", - Namespace: "some-ns", - }, - Spec: lokiv1.LokiStackSpec{ - Tenants: &lokiv1.TenantsSpec{ - Mode: lokiv1.Static, - Authentication: []lokiv1.AuthenticationSpec{ +func TestGetTenantSecrets(t *testing.T) { + for _, mode := range []lokiv1.ModeType{lokiv1.Static, lokiv1.Dynamic} { + for _, tc := range []struct { + name string + authNSpec []lokiv1.AuthenticationSpec + object client.Object + expected []*manifests.TenantSecrets + }{ + { + name: "oidc", + authNSpec: []lokiv1.AuthenticationSpec{ { TenantName: "test", TenantID: "test", @@ -45,105 +39,98 @@ func TestGetTenantSecrets_StaticMode(t *testing.T) { }, }, }, - }, - }, - } - - k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { - if name.Name == "test" && name.Namespace == "some-ns" { - k.SetClientObject(object, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "some-ns", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "some-ns", + }, + Data: map[string][]byte{ + "clientID": []byte("test"), + "clientSecret": []byte("test"), + "issuerCAPath": []byte("/path/to/ca/file"), + }, }, - Data: map[string][]byte{ - "clientID": []byte("test"), - "clientSecret": []byte("test"), - "issuerCAPath": []byte("/path/to/ca/file"), + expected: []*manifests.TenantSecrets{ + { + TenantName: "test", + OIDCSecret: &manifests.OIDCSecret{ + ClientID: "test", + ClientSecret: "test", + IssuerCAPath: "/path/to/ca/file", + }, + }, }, - }) - } - return nil - } - - ts, err := GetTenantSecrets(context.TODO(), k, r, s) - require.NoError(t, err) - - expected := []*manifests.TenantSecrets{ - { - TenantName: "test", - ClientID: "test", - ClientSecret: "test", - IssuerCAPath: "/path/to/ca/file", - }, - } - require.ElementsMatch(t, ts, expected) -} - -func TestGetTenantSecrets_DynamicMode(t *testing.T) { - k := &k8sfakes.FakeClient{} - r := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: "my-stack", - Namespace: "some-ns", - }, - } - - s := &lokiv1.LokiStack{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mystack", - Namespace: "some-ns", - }, - Spec: lokiv1.LokiStackSpec{ - Tenants: &lokiv1.TenantsSpec{ - Mode: lokiv1.Dynamic, - Authentication: []lokiv1.AuthenticationSpec{ + }, + { + name: "mTLS", + authNSpec: []lokiv1.AuthenticationSpec{ { TenantName: "test", TenantID: "test", - OIDC: &lokiv1.OIDCSpec{ - Secret: &lokiv1.TenantSecretSpec{ - Name: "test", + MTLS: &lokiv1.MTLSSpec{ + CA: &lokiv1.CASpec{ + CA: "test", + CAKey: "special-ca.crt", }, }, }, }, - }, - }, - } - - k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { - if name.Name == "test" && name.Namespace == "some-ns" { - k.SetClientObject(object, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "some-ns", + object: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "some-ns", + }, + Data: map[string]string{ + "special-ca.crt": "my-specila-ca", + }, }, - Data: map[string][]byte{ - "clientID": []byte("test"), - "clientSecret": []byte("test"), - "issuerCAPath": []byte("/path/to/ca/file"), + expected: []*manifests.TenantSecrets{ + { + TenantName: "test", + MTLSSecret: &manifests.MTLSSecret{ + CAPath: "/var/run/tls/tenants/test/special-ca.crt", + }, + }, }, + }, + } { + t.Run(strings.Join([]string{string(mode), tc.name}, "_"), func(t *testing.T) { + k := &k8sfakes.FakeClient{} + r := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: "my-stack", + Namespace: "some-ns", + }, + } + + s := &lokiv1.LokiStack{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mystack", + Namespace: "some-ns", + }, + Spec: lokiv1.LokiStackSpec{ + Tenants: &lokiv1.TenantsSpec{ + Mode: mode, + Authentication: tc.authNSpec, + }, + }, + } + + k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { + if name.Name == "test" && name.Namespace == "some-ns" { + k.SetClientObject(object, tc.object) + } + return nil + } + ts, err := GetTenantSecrets(context.TODO(), k, r, s) + require.NoError(t, err) + require.ElementsMatch(t, ts, tc.expected) }) } - return nil } - - ts, err := GetTenantSecrets(context.TODO(), k, r, s) - require.NoError(t, err) - - expected := []*manifests.TenantSecrets{ - { - TenantName: "test", - ClientID: "test", - ClientSecret: "test", - IssuerCAPath: "/path/to/ca/file", - }, - } - require.ElementsMatch(t, ts, expected) } -func TestExtractSecret(t *testing.T) { +func TestExtractOIDCSecret(t *testing.T) { type test struct { name string tenantName string @@ -174,7 +161,47 @@ 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) + } + if tst.wantErr { + require.NotNil(t, err) + } + }) + } +} + +func TestCheckKeyIsPresent(t *testing.T) { + type test struct { + name string + tenantName string + configMap *corev1.ConfigMap + wantErr bool + } + table := []test{ + { + name: "missing key", + tenantName: "tenant-a", + configMap: &corev1.ConfigMap{}, + wantErr: true, + }, + { + name: "all set", + tenantName: "tenant-a", + configMap: &corev1.ConfigMap{ + Data: map[string]string{ + "test": "test", + }, + }, + }, + } + for _, tst := range table { + tst := tst + t.Run(tst.name, func(t *testing.T) { + t.Parallel() + + err := checkKeyIsPresent(tst.configMap, "test") if !tst.wantErr { require.NoError(t, err) } diff --git a/operator/internal/handlers/lokistack_create_or_update_test.go b/operator/internal/handlers/lokistack_create_or_update_test.go index 7c5a099d30805..179f5fad9914a 100644 --- a/operator/internal/handlers/lokistack_create_or_update_test.go +++ b/operator/internal/handlers/lokistack_create_or_update_test.go @@ -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", + }, }, }, }, @@ -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, + }, }, }, }, diff --git a/operator/internal/manifests/gateway.go b/operator/internal/manifests/gateway.go index 1e1d3bffe89d0..16942d30ee853 100644 --- a/operator/internal/manifests/gateway.go +++ b/operator/internal/manifests/gateway.go @@ -76,12 +76,11 @@ 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, opts.Stack.Tenants, opts.Gates, minTLSVersion, ciphers); err != nil { return nil, err } - if err := configureGatewayServiceForMode(&svc.Spec, mode); err != nil { + if err := configureGatewayServiceForMode(&svc.Spec, opts.Stack.Tenants.Mode); err != nil { return nil, err } @@ -441,13 +440,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) } diff --git a/operator/internal/manifests/gateway_tenants.go b/operator/internal/manifests/gateway_tenants.go index 76dd2d5df149b..83e9e7d367995 100644 --- a/operator/internal/manifests/gateway_tenants.go +++ b/operator/internal/manifests/gateway_tenants.go @@ -1,6 +1,8 @@ package manifests import ( + "strings" + "github.com/ViaQ/logerr/v2/kverrors" "github.com/imdario/mergo" @@ -11,6 +13,7 @@ import ( "github.com/grafana/loki/operator/internal/manifests/internal/config" "github.com/grafana/loki/operator/internal/manifests/openshift" + routev1 "github.com/openshift/api/route/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" @@ -62,13 +65,16 @@ func ApplyGatewayDefaultOptions(opts *Options) error { return nil } -func configureGatewayDeploymentForMode(d *appsv1.Deployment, mode lokiv1.ModeType, fg configv1.FeatureGates, minTLSVersion string, ciphers string) error { - switch mode { +func configureGatewayDeploymentForMode(d *appsv1.Deployment, tenants *lokiv1.TenantsSpec, fg configv1.FeatureGates, minTLSVersion string, ciphers string) error { + switch tenants.Mode { case lokiv1.Static, lokiv1.Dynamic: - return nil // nothing to configure + if tenants != nil { + return configureMTLS(d, tenants) + } + return nil case lokiv1.OpenshiftLogging, lokiv1.OpenshiftNetwork: tlsDir := gatewayServerHTTPTLSDir() - return openshift.ConfigureGatewayDeployment(d, mode, tlsSecretVolume, tlsDir, minTLSVersion, ciphers, fg.HTTPEncryption) + return openshift.ConfigureGatewayDeployment(d, tenants.Mode, tlsSecretVolume, tlsDir, minTLSVersion, ciphers, fg.HTTPEncryption) } return nil @@ -120,7 +126,19 @@ func configureGatewayObjsForMode(objs []client.Object, opts Options) []client.Ob switch opts.Stack.Tenants.Mode { case lokiv1.Static, lokiv1.Dynamic: - // nothing to configure + // If a single tenant configure mTLS change Route termination policy + // to Passthrough + for _, o := range objs { + switch r := o.(type) { + case *routev1.Route: + for _, secret := range opts.Tenants.Secrets { + if secret.MTLSSecret != nil { + r.Spec.TLS.Termination = routev1.TLSTerminationPassthrough + break + } + } + } + } case lokiv1.OpenshiftLogging, lokiv1.OpenshiftNetwork: for _, o := range objs { switch sa := o.(type) { @@ -175,3 +193,64 @@ func ConfigureOptionsForMode(cfg *config.Options, opt Options) error { return nil } + +// configureMTLS will mount CA bundles and fix CLI arguments for the gateway container +// if any tenant configured mTLS authentication +func configureMTLS(d *appsv1.Deployment, tenants *lokiv1.TenantsSpec) error { + var gwIndex int + for i, c := range d.Spec.Template.Spec.Containers { + if c.Name == gatewayContainerName { + gwIndex = i + break + } + } + + gwContainer := d.Spec.Template.Spec.Containers[gwIndex].DeepCopy() + gwArgs := gwContainer.Args + gwVolumes := d.Spec.Template.Spec.Volumes + + mTLS := false + for _, tenant := range tenants.Authentication { + if tenant.MTLS != nil { + gwContainer.VolumeMounts = append(gwContainer.VolumeMounts, corev1.VolumeMount{ + Name: tenantMTLSVolumeName(tenant.TenantName), + MountPath: tenantMTLSCADir(tenant.TenantName), + }) + gwVolumes = append(gwVolumes, corev1.Volume{ + Name: tenantMTLSVolumeName(tenant.TenantName), + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: tenant.MTLS.CA.CA, + }, + }, + }, + }) + mTLS = true + } + } + if !mTLS { + return nil // nothing to configure + } + + // Remove old tls.client-auth-type + for i, arg := range gwArgs { + if strings.HasPrefix(arg, "--tls.client-auth-type=") { + gwArgs = append(gwArgs[:i], gwArgs[i+1:]...) + break + } + } + gwArgs = append(gwArgs, "--tls.client-auth-type=RequestClientCert") + + gwContainer.Args = gwArgs + p := corev1.PodSpec{ + Containers: []corev1.Container{ + *gwContainer, + }, + Volumes: gwVolumes, + } + if err := mergo.Merge(&d.Spec.Template.Spec, p, mergo.WithOverride); err != nil { + return kverrors.Wrap(err, "failed to merge server pki into container spec ") + } + return nil +} diff --git a/operator/internal/manifests/gateway_tenants_test.go b/operator/internal/manifests/gateway_tenants_test.go index 843194954a01b..e26beb2cd16c4 100644 --- a/operator/internal/manifests/gateway_tenants_test.go +++ b/operator/internal/manifests/gateway_tenants_test.go @@ -18,6 +18,25 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) +func defaultGatewayDeployment() *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: gatewayContainerName, + }, + }, + }, + }, + }, + } +} + func TestApplyGatewayDefaultsOptions(t *testing.T) { type tt struct { desc string @@ -397,32 +416,50 @@ func TestApplyGatewayDefaultsOptions(t *testing.T) { func TestConfigureDeploymentForMode(t *testing.T) { type tt struct { desc string - mode lokiv1.ModeType stackName string stackNs string featureGates configv1.FeatureGates + tenants *lokiv1.TenantsSpec dpl *appsv1.Deployment want *appsv1.Deployment } tc := []tt{ { - desc: "static mode", - mode: lokiv1.Static, - dpl: &appsv1.Deployment{}, - want: &appsv1.Deployment{}, + desc: "static mode without tenants", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Static, + }, + dpl: defaultGatewayDeployment(), + want: defaultGatewayDeployment(), }, { desc: "dynamic mode", - mode: lokiv1.Dynamic, - dpl: &appsv1.Deployment{}, - want: &appsv1.Deployment{}, + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Dynamic, + }, + dpl: defaultGatewayDeployment(), + want: defaultGatewayDeployment(), }, { - desc: "openshift-logging mode", - mode: lokiv1.OpenshiftLogging, + desc: "static mode with mTLS tenant configured", stackName: "test", stackNs: "test-ns", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Static, + Authentication: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "a", + MTLS: &lokiv1.MTLSSpec{ + CA: &lokiv1.CASpec{ + CA: "my-ca", + CAKey: "my-ca-key", + }, + }, + }, + }, + }, dpl: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test-ns", @@ -433,12 +470,113 @@ func TestConfigureDeploymentForMode(t *testing.T) { Containers: []corev1.Container{ { Name: gatewayContainerName, + Args: []string{"--tls.client-auth-type=NoClientCert"}, + }, + }, + }, + }, + }, + }, + want: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: gatewayContainerName, + Args: []string{"--tls.client-auth-type=RequestClientCert"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "test-a-ca-bundle", + MountPath: "/var/run/tls/tenants/test-a", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "test-a-ca-bundle", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-ca", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "dynamic mode with mTLS tenant configured", + stackName: "test", + stackNs: "test-ns", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Dynamic, + Authentication: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "a", + MTLS: &lokiv1.MTLSSpec{ + CA: &lokiv1.CASpec{ + CA: "my-ca", + CAKey: "my-ca-key", + }, + }, + }, + }, + }, + dpl: defaultGatewayDeployment(), + want: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: gatewayContainerName, + Args: []string{"--tls.client-auth-type=RequestClientCert"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "test-a-ca-bundle", + MountPath: "/var/run/tls/tenants/test-a", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "test-a-ca-bundle", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-ca", + }, + }, + }, }, }, }, }, }, }, + }, + { + desc: "openshift-logging mode", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.OpenshiftLogging, + }, + stackName: "test", + stackNs: "test-ns", + dpl: defaultGatewayDeployment(), want: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test-ns", @@ -510,8 +648,10 @@ func TestConfigureDeploymentForMode(t *testing.T) { }, }, { - desc: "openshift-logging mode with http encryption", - mode: lokiv1.OpenshiftLogging, + desc: "openshift-logging mode with http encryption", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.OpenshiftLogging, + }, stackName: "test", stackNs: "test-ns", featureGates: configv1.FeatureGates{ @@ -629,8 +769,10 @@ func TestConfigureDeploymentForMode(t *testing.T) { }, }, { - desc: "openshift-network mode", - mode: lokiv1.OpenshiftNetwork, + desc: "openshift-network mode", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.OpenshiftNetwork, + }, stackName: "test", stackNs: "test-ns", dpl: &appsv1.Deployment{ @@ -729,8 +871,10 @@ func TestConfigureDeploymentForMode(t *testing.T) { }, }, { - desc: "openshift-network mode with http encryption", - mode: lokiv1.OpenshiftNetwork, + desc: "openshift-network mode with http encryption", + tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.OpenshiftNetwork, + }, stackName: "test", stackNs: "test-ns", featureGates: configv1.FeatureGates{ @@ -849,7 +993,7 @@ func TestConfigureDeploymentForMode(t *testing.T) { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() - err := configureGatewayDeploymentForMode(tc.dpl, tc.mode, tc.featureGates, "min-version", "cipher1,cipher2") + err := configureGatewayDeploymentForMode(tc.dpl, tc.tenants, tc.featureGates, "min-version", "cipher1,cipher2") require.NoError(t, err) require.Equal(t, tc.want, tc.dpl) }) diff --git a/operator/internal/manifests/gateway_test.go b/operator/internal/manifests/gateway_test.go index 00e2c0b93b6ff..720b72f5b2fdf 100644 --- a/operator/internal/manifests/gateway_test.go +++ b/operator/internal/manifests/gateway_test.go @@ -194,10 +194,12 @@ func TestGatewayConfigMap_ReturnsSHA1OfBinaryContents(t *testing.T) { Tenants: Tenants{ Secrets: []*TenantSecrets{ { - TenantName: "test", - ClientID: "test", - ClientSecret: "test", - IssuerCAPath: "/tmp/test", + TenantName: "test", + OIDCSecret: &OIDCSecret{ + ClientID: "test", + ClientSecret: "test", + IssuerCAPath: "/tmp/test", + }, }, }, }, diff --git a/operator/internal/manifests/internal/gateway/build.go b/operator/internal/manifests/internal/gateway/build.go index 1e1a4cb0d97b6..659e4e32c6acb 100644 --- a/operator/internal/manifests/internal/gateway/build.go +++ b/operator/internal/manifests/internal/gateway/build.go @@ -34,7 +34,9 @@ var ( lokiGatewayRbacYAMLTmpl = template.Must(template.ParseFS(lokiGatewayRbacYAMLTmplFile, "gateway-rbac.yaml")) - lokiGatewayTenantsYAMLTmpl = template.Must(template.ParseFS(lokiGatewayTenantsYAMLTmplFile, "gateway-tenants.yaml")) + lokiGatewayTenantsYAMLTmpl = template.Must(template.New("gateway-tenants.yaml").Funcs(template.FuncMap{ + "make_array": func(els ...any) []any { return els }, + }).ParseFS(lokiGatewayTenantsYAMLTmplFile, "gateway-tenants.yaml")) lokiStackGatewayRegoTmpl = template.Must(template.ParseFS(lokiStackGatewayRegoTmplFile, "lokistack-gateway.rego")) ) diff --git a/operator/internal/manifests/internal/gateway/build_test.go b/operator/internal/manifests/internal/gateway/build_test.go index 7922a0e499fb7..3ee2e6e4ce82f 100644 --- a/operator/internal/manifests/internal/gateway/build_test.go +++ b/operator/internal/manifests/internal/gateway/build_test.go @@ -9,7 +9,64 @@ import ( ) func TestBuild_StaticMode(t *testing.T) { - expTntCfg := ` + for _, tc := range []struct { + name string + authNSpec []lokiv1.AuthenticationSpec + tenantSecrets []*Secret + authZSpec *lokiv1.AuthorizationSpec + expTntCfg string + expRbacCfg string + }{ + { + name: "oidc", + authNSpec: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "test", + OIDC: &lokiv1.OIDCSpec{ + Secret: &lokiv1.TenantSecretSpec{ + Name: "test", + }, + IssuerURL: "https://127.0.0.1:5556/dex", + RedirectURL: "https://localhost:8443/oidc/test-a/callback", + GroupClaim: "test", + UsernameClaim: "test", + }, + }, + }, + tenantSecrets: []*Secret{ + { + TenantName: "test-a", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "test123", + IssuerCAPath: "/tmp/ca/path", + }, + }, + }, + authZSpec: &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"}, + }, + }, + }, + expTntCfg: ` tenants: - name: test-a id: test @@ -26,8 +83,8 @@ tenants: paths: - /etc/lokistack-gateway/rbac.yaml - /etc/lokistack-gateway/lokistack-gateway.rego -` - expRbacCfg := ` +`, + expRbacCfg: ` roleBindings: - name: test-a roles: @@ -43,70 +100,140 @@ roles: - metrics tenants: - test-a -` - opts := Options{ - Stack: lokiv1.LokiStackSpec{ - Tenants: &lokiv1.TenantsSpec{ - Mode: lokiv1.Static, - Authentication: []lokiv1.AuthenticationSpec{ - { - TenantName: "test-a", - TenantID: "test", - OIDC: &lokiv1.OIDCSpec{ - Secret: &lokiv1.TenantSecretSpec{ - Name: "test", - }, - IssuerURL: "https://127.0.0.1:5556/dex", - RedirectURL: "https://localhost:8443/oidc/test-a/callback", - GroupClaim: "test", - UsernameClaim: "test", +`, + }, + { + name: "mTLS", + authNSpec: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "test", + MTLS: &lokiv1.MTLSSpec{ + CA: &lokiv1.CASpec{ + CA: "my-custom-ca", + CAKey: "special-ca.crt", }, }, }, - Authorization: &lokiv1.AuthorizationSpec{ - Roles: []lokiv1.RoleSpec{ - { - Name: "some-name", - Resources: []string{"metrics"}, - Tenants: []string{"test-a"}, - Permissions: []lokiv1.PermissionType{"read"}, - }, + }, + tenantSecrets: []*Secret{ + { + TenantName: "test-a", + MTLS: &MTLS{ + CAPath: "/var/run/tls/tenants/test-a/special-ca.crt", + }, + }, + }, + authZSpec: &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", - }, + }, + RoleBindings: []lokiv1.RoleBindingsSpec{ + { + Name: "test-a", + Subjects: []lokiv1.Subject{ + { + Name: "test@example.com", + Kind: "user", }, - Roles: []string{"read-write"}, }, + Roles: []string{"read-write"}, }, }, }, + expTntCfg: ` +tenants: +- name: test-a + id: test + mTLS: + caPath: /var/run/tls/tenants/test-a/special-ca.crt + opa: + query: data.lokistack.allow + paths: + - /etc/lokistack-gateway/rbac.yaml + - /etc/lokistack-gateway/lokistack-gateway.rego +`, + expRbacCfg: ` +roleBindings: +- name: test-a + roles: + - read-write + subjects: + - kind: user + name: test@example.com +roles: +- name: some-name + permissions: + - read + resources: + - metrics + tenants: + - test-a +`, }, - Namespace: "test-ns", - Name: "test", - TenantSecrets: []*Secret{ - { - TenantName: "test-a", - ClientID: "test", - ClientSecret: "test123", - IssuerCAPath: "/tmp/ca/path", - }, - }, + } { + t.Run(tc.name, func(t *testing.T) { + opts := Options{ + Stack: lokiv1.LokiStackSpec{ + Tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Static, + Authentication: tc.authNSpec, + Authorization: tc.authZSpec, + }, + }, + Namespace: "test-ns", + Name: "test", + TenantSecrets: tc.tenantSecrets, + } + rbacConfig, tenantsConfig, regoCfg, err := Build(opts) + require.NoError(t, err) + require.YAMLEq(t, tc.expTntCfg, string(tenantsConfig)) + require.YAMLEq(t, tc.expRbacCfg, string(rbacConfig)) + require.NotEmpty(t, regoCfg) + }) } - rbacConfig, tenantsConfig, regoCfg, err := Build(opts) - require.NoError(t, err) - require.YAMLEq(t, expTntCfg, string(tenantsConfig)) - require.YAMLEq(t, expRbacCfg, string(rbacConfig)) - require.NotEmpty(t, regoCfg) } func TestBuild_DynamicMode(t *testing.T) { - expTntCfg := ` + for _, tc := range []struct { + name string + authNSpec []lokiv1.AuthenticationSpec + tenantSecrets []*Secret + expTntCfg string + }{ + { + name: "oidc", + authNSpec: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "test", + OIDC: &lokiv1.OIDCSpec{ + Secret: &lokiv1.TenantSecretSpec{ + Name: "test", + }, + IssuerURL: "https://127.0.0.1:5556/dex", + RedirectURL: "https://localhost:8443/oidc/test-a/callback", + GroupClaim: "test", + UsernameClaim: "test", + }, + }, + }, + tenantSecrets: []*Secret{ + { + TenantName: "test-a", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "test123", + IssuerCAPath: "/tmp/ca/path", + }, + }, + }, + expTntCfg: ` tenants: - name: test-a id: test @@ -120,49 +247,65 @@ tenants: groupClaim: test opa: url: http://127.0.0.1:8181/v1/data/observatorium/allow -` - opts := Options{ - Stack: lokiv1.LokiStackSpec{ - Tenants: &lokiv1.TenantsSpec{ - Mode: lokiv1.Dynamic, - Authentication: []lokiv1.AuthenticationSpec{ - { - TenantName: "test-a", - TenantID: "test", - OIDC: &lokiv1.OIDCSpec{ - Secret: &lokiv1.TenantSecretSpec{ - Name: "test", - }, - IssuerURL: "https://127.0.0.1:5556/dex", - RedirectURL: "https://localhost:8443/oidc/test-a/callback", - GroupClaim: "test", - UsernameClaim: "test", +`, + }, + { + name: "mTLS", + authNSpec: []lokiv1.AuthenticationSpec{ + { + TenantName: "test-a", + TenantID: "test", + MTLS: &lokiv1.MTLSSpec{ + CA: &lokiv1.CASpec{ + CA: "my-custom-ca", + CAKey: "special-ca.crt", }, }, }, - Authorization: &lokiv1.AuthorizationSpec{ - OPA: &lokiv1.OPASpec{ - URL: "http://127.0.0.1:8181/v1/data/observatorium/allow", + }, + tenantSecrets: []*Secret{ + { + TenantName: "test-a", + MTLS: &MTLS{ + CAPath: "/var/run/tls/tenants/test-a/special-ca.crt", }, }, }, + expTntCfg: ` +tenants: +- name: test-a + id: test + mTLS: + caPath: /var/run/tls/tenants/test-a/special-ca.crt + opa: + url: http://127.0.0.1:8181/v1/data/observatorium/allow +`, }, - Namespace: "test-ns", - Name: "test", - TenantSecrets: []*Secret{ - { - TenantName: "test-a", - ClientID: "test", - ClientSecret: "test123", - IssuerCAPath: "/tmp/ca/path", - }, - }, + } { + t.Run(tc.name, func(t *testing.T) { + opts := Options{ + Stack: lokiv1.LokiStackSpec{ + Tenants: &lokiv1.TenantsSpec{ + Mode: lokiv1.Dynamic, + Authentication: tc.authNSpec, + Authorization: &lokiv1.AuthorizationSpec{ + OPA: &lokiv1.OPASpec{ + URL: "http://127.0.0.1:8181/v1/data/observatorium/allow", + }, + }, + }, + }, + Namespace: "test-ns", + Name: "test", + TenantSecrets: tc.tenantSecrets, + } + rbacConfig, tenantsConfig, regoCfg, err := Build(opts) + require.NoError(t, err) + require.YAMLEq(t, tc.expTntCfg, string(tenantsConfig)) + require.Empty(t, rbacConfig) + require.Empty(t, regoCfg) + }) } - rbacConfig, tenantsConfig, regoCfg, err := Build(opts) - require.NoError(t, err) - require.YAMLEq(t, expTntCfg, string(tenantsConfig)) - require.Empty(t, rbacConfig) - require.Empty(t, regoCfg) } func TestBuild_OpenshiftLoggingMode(t *testing.T) { @@ -234,22 +377,28 @@ tenants: Name: "test", TenantSecrets: []*Secret{ { - TenantName: "application", - ClientID: "test", - ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", - IssuerCAPath: "./tmp/certs/ca.pem", + TenantName: "application", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + IssuerCAPath: "./tmp/certs/ca.pem", + }, }, { - TenantName: "infrastructure", - ClientID: "test", - ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", - IssuerCAPath: "./tmp/certs/ca.pem", + TenantName: "infrastructure", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + IssuerCAPath: "./tmp/certs/ca.pem", + }, }, { - TenantName: "audit", - ClientID: "test", - ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", - IssuerCAPath: "./tmp/certs/ca.pem", + TenantName: "audit", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + IssuerCAPath: "./tmp/certs/ca.pem", + }, }, }, } @@ -298,10 +447,12 @@ tenants: Name: "test", TenantSecrets: []*Secret{ { - TenantName: "network", - ClientID: "test", - ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", - IssuerCAPath: "./tmp/certs/ca.pem", + TenantName: "network", + OIDC: &OIDC{ + ClientID: "test", + ClientSecret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + IssuerCAPath: "./tmp/certs/ca.pem", + }, }, }, } diff --git a/operator/internal/manifests/internal/gateway/gateway-tenants.yaml b/operator/internal/manifests/internal/gateway/gateway-tenants.yaml index a248e3c443974..aed6870231f60 100644 --- a/operator/internal/manifests/internal/gateway/gateway-tenants.yaml +++ b/operator/internal/manifests/internal/gateway/gateway-tenants.yaml @@ -1,25 +1,26 @@ -tenants: -{{- if $l := . -}} -{{- if eq $l.Stack.Tenants.Mode "static" -}} -{{- range $spec := $l.Stack.Tenants.Authentication }} -- name: {{ $spec.TenantName }} - id: {{ $spec.TenantID }} - oidc: - {{- range $secret := $l.TenantSecrets }} - {{- if eq $secret.TenantName $spec.TenantName -}} - {{ if $secret.ClientID }} - clientID: {{ $secret.ClientID }} +{{- define "mTLS" }} + {{- $secret := index . 0 -}} + mTLS: + {{ if $secret.MTLS.CAPath }} + caPath: {{ $secret.MTLS.CAPath }} {{- end -}} - {{ if $secret.ClientSecret }} - clientSecret: {{ $secret.ClientSecret }} - {{- end -}} - {{ if $secret.IssuerCAPath }} - issuerCAPath: {{ $secret.IssuerCAPath }} +{{- end }} + +{{- define "oidc" }} + {{- $secret := index . 0 -}} + {{- $spec := index . 1 -}} + oidc: + {{- if $secret.OIDC.ClientID }} + clientID: {{ $secret.OIDC.ClientID }} {{- end -}} + {{ if $secret.OIDC.ClientSecret }} + clientSecret: {{ $secret.OIDC.ClientSecret }} {{- end -}} + {{ if $secret.OIDC.IssuerCAPath }} + issuerCAPath: {{ $secret.OIDC.IssuerCAPath }} {{- end }} issuerURL: {{ $spec.OIDC.IssuerURL }} - {{ if $spec.OIDC.RedirectURL }} + {{- if $spec.OIDC.RedirectURL }} redirectURL: {{ $spec.OIDC.RedirectURL }} {{- end -}} {{ if $spec.OIDC.UsernameClaim }} @@ -28,6 +29,29 @@ tenants: {{- if $spec.OIDC.GroupClaim }} groupClaim: {{ $spec.OIDC.GroupClaim }} {{- end }} +{{- end }} + +{{- define "authorization" }} + {{- $l := index . 0 -}} + {{- $spec := index . 1 -}} + {{- range $secret := $l.TenantSecrets }} + {{- if eq $secret.TenantName $spec.TenantName }} + {{- if $secret.OIDC }} + {{ template "oidc" (make_array $secret $spec) }} + {{- else if $secret.MTLS }} + {{ template "mTLS" (make_array $secret) }} + {{- end -}} + {{- end -}} + {{- end }} +{{- end }} + +tenants: +{{- if $l := . -}} +{{- if eq $l.Stack.Tenants.Mode "static" -}} +{{- range $spec := $l.Stack.Tenants.Authentication }} +- name: {{ $spec.TenantName }} + id: {{ $spec.TenantID }} + {{ template "authorization" (make_array $l $spec) }} opa: query: data.lokistack.allow paths: @@ -39,28 +63,7 @@ tenants: {{- range $spec := $tenant.Authentication }} - name: {{ $spec.TenantName }} id: {{ $spec.TenantID }} - oidc: - {{- range $secret := $l.TenantSecrets }} - {{- if eq $secret.TenantName $spec.TenantName -}} - {{ if $secret.ClientID }} - clientID: {{ $secret.ClientID }} - {{- end -}} - {{ if $secret.ClientSecret }} - clientSecret: {{ $secret.ClientSecret }} - {{- end -}} - {{ if $secret.IssuerCAPath }} - issuerCAPath: {{ $secret.IssuerCAPath }} - {{- end -}} - {{- end -}} - {{- end }} - issuerURL: {{ $spec.OIDC.IssuerURL }} - redirectURL: {{ $spec.OIDC.RedirectURL }} - {{- if $spec.OIDC.UsernameClaim }} - usernameClaim: {{ $spec.OIDC.UsernameClaim }} - {{- end -}} - {{- if $spec.OIDC.GroupClaim }} - groupClaim: {{ $spec.OIDC.GroupClaim }} - {{- end }} + {{ template "authorization" (make_array $l $spec) }} opa: url: {{ $tenant.Authorization.OPA.URL }} {{- end -}} diff --git a/operator/internal/manifests/internal/gateway/options.go b/operator/internal/manifests/internal/gateway/options.go index f3f5d1769ee04..eefefedcfbca6 100644 --- a/operator/internal/manifests/internal/gateway/options.go +++ b/operator/internal/manifests/internal/gateway/options.go @@ -17,10 +17,21 @@ type Options struct { TenantSecrets []*Secret } -// Secret for clientID, clientSecret and issuerCAPath for tenant's authentication. +// Secret for tenant's authentication. type Secret struct { - TenantName string + TenantName string + OIDC *OIDC + MTLS *MTLS +} + +// OIDC secret for tenant's authentication. +type OIDC struct { ClientID string ClientSecret string IssuerCAPath string } + +// MTLS config for tenant's authentication. +type MTLS struct { + CAPath string +} diff --git a/operator/internal/manifests/options.go b/operator/internal/manifests/options.go index 4f4e32f947dbe..1e419d60852dd 100644 --- a/operator/internal/manifests/options.go +++ b/operator/internal/manifests/options.go @@ -65,14 +65,23 @@ type Tenants struct { Configs map[string]TenantConfig } -// TenantSecrets for clientID, clientSecret and issuerCAPath for tenant's authentication. +// TenantSecrets for tenant's authentication. type TenantSecrets struct { - TenantName string + TenantName string + OIDCSecret *OIDCSecret + MTLSSecret *MTLSSecret +} + +type OIDCSecret struct { ClientID string ClientSecret string IssuerCAPath string } +type MTLSSecret struct { + CAPath string +} + // TenantConfig for tenant authorizationconfig type TenantConfig struct { OIDC *TenantOIDCSpec diff --git a/operator/internal/manifests/var.go b/operator/internal/manifests/var.go index 140f4ec14a6c4..71a26d257078e 100644 --- a/operator/internal/manifests/var.go +++ b/operator/internal/manifests/var.go @@ -97,6 +97,8 @@ const ( httpTLSDir = "/var/run/tls/http" // grpcTLSDir is the path that is mounted from the secret for TLS grpcTLSDir = "/var/run/tls/grpc" + // tenantMTLSDir is the path that is mounted from the configmaps for mTLS + tenantMTLSDir = "/var/run/tls/tenants" // LokiStackCABundleDir is the path that is mounted from the configmap for TLS caBundleDir = "/var/run/ca" // caFile is the file name of the certificate authority file @@ -279,6 +281,18 @@ func gatewayUpstreamHTTPTLSKey() string { return path.Join(gatewayUpstreamHTTPTLSDir(), corev1.TLSPrivateKeyKey) } +func tenantMTLSVolumeName(tenantName string) string { + return fmt.Sprintf("%s-ca-bundle", tenantName) +} + +func tenantMTLSCADir(tennantName string) string { + return path.Join(tenantMTLSDir, tennantName) +} + +func TenantMTLSCAPath(tennantName, key string) string { + return path.Join(tenantMTLSDir, tennantName, key) +} + func gatewayClientSecretName(stackName string) string { return fmt.Sprintf("%s-gateway-client-http", stackName) }