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 1 commit
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
23 changes: 20 additions & 3 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"`
}

// TLSConfigSpec specifies safe TLS configuration parameters.
type TLSConfigSpec struct {
// Secret defines the spec for the custom CA and custom server certificate for tenant's authentication.
//
// +required
// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Tenant Secret"
Secret *TenantSecretSpec `json:"secret"`
}

// 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="TLS Configuration"
TLSConfig *TLSConfigSpec `json:"tlsConfig,omitempty"`
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
}

// ModeType is the authentication/authorization mode in which LokiStack Gateway will be configured.
Expand Down Expand Up @@ -928,6 +943,8 @@ const (
ReasonMissingGatewayTenantSecret LokiStackConditionReason = "MissingGatewayTenantSecret"
// ReasonInvalidGatewayTenantSecret when the format of the secret is invalid.
ReasonInvalidGatewayTenantSecret LokiStackConditionReason = "InvalidGatewayTenantSecret"
// 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
52 changes: 52 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.

87 changes: 70 additions & 17 deletions operator/internal/handlers/internal/gateway/tenant_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,72 @@ func GetTenantSecrets(
)

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, tenant.TenantName)
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)
}
tenantSecrets = append(tenantSecrets, &manifests.TenantSecrets{
OIDCSecret: oidcSecret,
})
case tenant.TLSConfig != nil:
key := client.ObjectKey{Name: tenant.TLSConfig.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)
}

var ts *manifests.TenantSecrets
ts, err := extractSecret(&gatewaySecret, tenant.TenantName)
if err != nil {
TLSSecret, err := extractTLSSecret(&gatewaySecret, tenant.TenantName)
if err != nil {
return nil, &status.DegradedError{
Message: "Invalid gateway tenant secret contents",
Reason: lokiv1.ReasonInvalidGatewayTenantSecret,
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
Requeue: true,
}
}
tenantSecrets = append(tenantSecrets, &manifests.TenantSecrets{
TenantName: tenant.TenantName,
TLSSecret: TLSSecret,
})
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 +107,27 @@ 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
}

// extractTLSSecret reads a k8s secret into a manifest tenant secret struct if valid.
func extractTLSSecret(s *corev1.Secret, tenantName string) (*manifests.TLSSecret, error) {
// Extract and validate mandatory fields
cert := s.Data["cert"]
if len(cert) == 0 {
return nil, kverrors.New("missing cert field", "field", "cert")
}
ca := s.Data["ca"]
if len(ca) == 0 {
return nil, kverrors.New("missing ca field", "field", "ca")
}
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved

return &manifests.TLSSecret{
Cert: string(cert),
CA: string(ca),
}, nil
}
JoaoBraveCoding marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,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, tst.tenantName)
if !tst.wantErr {
require.NoError(t, err)
}
Expand Down
16 changes: 11 additions & 5 deletions operator/internal/manifests/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,18 @@ func gatewayConfigObjs(opt Options) (*corev1.ConfigMap, *corev1.Secret, string,
func gatewayConfigOptions(opt Options) gateway.Options {
var gatewaySecrets []*gateway.Secret
for _, secret := range opt.Tenants.Secrets {
gatewaySecret := &gateway.Secret{
TenantName: secret.TenantName,
ClientID: secret.ClientID,
ClientSecret: secret.ClientSecret,
IssuerCAPath: secret.IssuerCAPath,
switch {
case secret.OIDCSecret != nil:
gatewaySecret := &gateway.Secret{
TenantName: secret.TenantName,
OIDC: &gateway.OIDC{
ClientID: secret.ClientID,
ClientSecret: secret.ClientSecret,
IssuerCAPath: secret.IssuerCAPath,
},
}
}
gatewaySecret := &gateway.Secret{}
gatewaySecrets = append(gatewaySecrets, gatewaySecret)
}

Expand Down
14 changes: 12 additions & 2 deletions operator/internal/manifests/internal/gateway/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ 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
TLS *TLS
}

type OIDC struct {
ClientID string
ClientSecret string
IssuerCAPath string
}

type TLS struct {
CA string
Cert string
}
14 changes: 12 additions & 2 deletions operator/internal/manifests/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,24 @@ 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
TLSSecret *TLSSecret
}

type OIDCSecret struct {
ClientID string
ClientSecret string
IssuerCAPath string
}

type TLSSecret struct {
CA string
Cert string
}

// TenantConfig for tenant authorizationconfig
type TenantConfig struct {
OIDC *TenantOIDCSpec
Expand Down