Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
41 changes: 39 additions & 2 deletions auth-callout/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,49 @@ serviceConfig:

### mTLS Configuration

For mTLS authentication, configure the CA certificate path:
For mTLS authentication, mount the CA Secret and set the auth-callout CA path:

```yaml
serviceConfig:
mtls:
ca-path: "/etc/ssl/certs/ca.crt"
ca-path: "/etc/mtls-ca/ca.crt"

extraVolumeMounts:
- name: mtls-ca
mountPath: /etc/mtls-ca
readOnly: true

extraVolumes:
- name: mtls-ca
secret:
secretName: my-mtls-ca
items:
- key: ca.crt
path: ca.crt
```

Parent charts can use templated snippets when the mount depends on values
visible to this subchart, such as `global`:

```yaml
extraVolumeMountTemplates:
- |
{{- if .Values.global.eventBus.mtls.enabled }}
- name: mtls-ca
mountPath: /etc/mtls-ca
readOnly: true
{{- end }}

extraVolumeTemplates:
- |
{{- if .Values.global.eventBus.mtls.enabled }}
- name: mtls-ca
secret:
secretName: my-mtls-ca
items:
- key: ca.crt
path: ca.crt
{{- end }}
```

## Permissions Configuration
Expand Down
12 changes: 12 additions & 0 deletions auth-callout/deploy/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ spec:
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- range $template := .Values.extraVolumeMountTemplates }}
{{- $rendered := tpl $template $ | trim }}
{{- if $rendered }}
{{ $rendered | nindent 12 }}
{{- end }}
{{- end }}

{{- if .Values.healthChecks.livenessProbe.enabled }}
livenessProbe:
Expand Down Expand Up @@ -169,3 +175,9 @@ spec:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- range $template := .Values.extraVolumeTemplates }}
{{- $rendered := tpl $template $ | trim }}
{{- if $rendered }}
{{ $rendered | nindent 8 }}
{{- end }}
{{- end }}
16 changes: 16 additions & 0 deletions auth-callout/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ extraVolumes:
# configMap:
# name: my-configmap

# Additional templated volume snippets.
# Use when volume presence depends on values visible to this chart.
extraVolumeTemplates: []
# - |
# - name: generated-volume
# secret:
# secretName: generated-secret

# Additional volume mounts to be added to the container
extraVolumeMounts:
[]
Expand All @@ -64,6 +72,14 @@ extraVolumeMounts:
# mountPath: /etc/secrets
# readOnly: true

# Additional templated volume mount snippets.
# Use when mount presence depends on values visible to this chart.
extraVolumeMountTemplates: []
# - |
# - name: generated-volume
# mountPath: /etc/generated
# readOnly: true

podAnnotations: {}
podLabels: {}

Expand Down
86 changes: 67 additions & 19 deletions auth-callout/src/internal/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"math/big"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -476,36 +480,31 @@ func TestOAuth2RequiredScope(t *testing.T) {

// TestMTLSAuthentication tests mTLS client certificate authentication
func TestMTLSAuthentication(t *testing.T) {
// Create a test permissions file
permFile := createTestPermissionsFile(t)
defer os.Remove(permFile)

pm, err := config.NewPermissionsManager(permFile, testLogger())
require.NoError(t, err)
defer pm.Close()

// Initialize mTLS authenticator without CA (for testing)
mtlsAuth, err := NewMTLSAuthenticator(nil, pm, testLogger(), testServiceName)
require.NoError(t, err)
caPEM, caKey := createTestCA(t)
clientCertPEM := createClientCert(t, "device1", caPEM, caKey)

// Test certificate (self-signed for testing)
testCertPEM := `-----BEGIN CERTIFICATE-----
MIICxjCCAa4CCQDFPx3qvE6Y1DANBgkqhkiG9w0BAQsFADAkMQswCQYDVQQGEwJV
UzEVMBMGA1UEAwwMQ049ZGV2aWNlMQ==
-----END CERTIFICATE-----`
mtlsAuth, err := NewMTLSAuthenticator(caPEM, pm, testLogger(), testServiceName)
require.NoError(t, err)

// This would fail without a valid cert, but tests the flow
profile, err := mtlsAuth.Authenticate(context.Background(), testCertPEM)
if err != nil {
t.Logf("mTLS authentication failed (expected with test cert): %v", err)
return
}
profile, err := mtlsAuth.Authenticate(context.Background(), clientCertPEM)
require.NoError(t, err)
require.NotNil(t, profile)
assert.Equal(t, "device1", profile.Name)
assert.Equal(t, "APP1", profile.Account)

if profile == nil {
t.Error("Expected non-nil profile")
}
otherCAPEM, otherCAKey := createTestCA(t)
untrustedCertPEM := createClientCert(t, "device1", otherCAPEM, otherCAKey)

t.Logf("mTLS authentication successful for profile: %s", profile.Name)
_, err = mtlsAuth.Authenticate(context.Background(), untrustedCertPEM)
require.Error(t, err)
assert.Contains(t, err.Error(), "certificate validation failed")
}

// TestNKeyAuthentication tests NKey authentication
Expand Down Expand Up @@ -768,3 +767,52 @@ func createTestPermissionsFile(t *testing.T) string {
tmpFile.Close()
return tmpFile.Name()
}

func createTestCA(t *testing.T) ([]byte, *rsa.PrivateKey) {
t.Helper()

key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{CommonName: "test-ca"},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}

der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)

return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), key
}

func createClientCert(t *testing.T, commonName string, caPEM []byte, caKey *rsa.PrivateKey) string {
t.Helper()

block, _ := pem.Decode(caPEM)
require.NotNil(t, block)

caCert, err := x509.ParseCertificate(block.Bytes)
require.NoError(t, err)

key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{CommonName: commonName},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}

der, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
require.NoError(t, err)

return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
}
5 changes: 3 additions & 2 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,16 @@ global:
enabled: true # default
```

When enabled, the chart mounts the `nats-mtls-server-tls` Secret's `ca.crt` into auth-callout at `/etc/mtls-ca/ca.crt` and sets `AUTH_CALLOUT_MTLS_CA_PATH` to that file.

Set `global.eventBus.mtls.enabled: false` to disable the mTLS NATS cluster. When disabled:

- The `nats-mtls` subchart is not rendered (no pods, services, or config)
- The `mqttMtls` gateway route is not created
- The `nats-mtls-accounts-config` ConfigMap is not created
- mTLS-specific keys are omitted from `nats-env-config`
- mTLS leaf nkey entries are omitted from the auth-callout permissions
- The mTLS CA Secret is not mounted into auth-callout
- The mTLS secrets (`nats-mtls-server-tls`, `nats-mtls-leaf`, `nats-mtls-authx-leaf`, `nats-mtls-sys-leaf`) are not required

## Subchart Configuration
Expand Down Expand Up @@ -551,8 +554,6 @@ auth-callout:
url: "https://keycloak.example.com/realms/event-bus/protocol/openid-connect/certs"
issuer: "https://keycloak.example.com/realms/event-bus"
audience: "dsx-exchange"
mtls:
ca-path: "/etc/mtls-ca/ca.crt"
```

### CSC Cluster
Expand Down
24 changes: 24 additions & 0 deletions deploy/nats-event-bus/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,30 @@ auth-callout:
extraEnvTemplates:
- |
{{ include "nats-event-bus.authCalloutEventBusEnv" . }}
{{- if .Values.global.eventBus.mtls.enabled }}
- name: AUTH_CALLOUT_MTLS_CA_PATH
value: "/etc/mtls-ca/ca.crt"
{{- end }}

# Umbrella-owned mTLS endpoint: wire auth-callout CA only when enabled.
extraVolumeMountTemplates:
- |
{{- if .Values.global.eventBus.mtls.enabled }}
- name: mtls-ca
mountPath: /etc/mtls-ca
readOnly: true
{{- end }}

extraVolumeTemplates:
- |
{{- if .Values.global.eventBus.mtls.enabled }}
- name: mtls-ca
secret:
secretName: nats-mtls-server-tls
items:
- key: ca.crt
path: ca.crt
{{- end }}

# Operator-defined auth-callout env vars.
# The generated permissions use nkey names leaf-cpc-{id} and
Expand Down
8 changes: 4 additions & 4 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ auth-callout:

BMS and OT devices connect to the mTLS NATS endpoint (port 8883) with a client certificate. TLS is terminated at the NATS pod (the Gateway API controller uses TCP passthrough for this listener). The auth-callout extracts the certificate's Common Name and matches it to a permissions entry.

Configure the CA certificate path:
The event-bus chart enables the mTLS endpoint by default. When `global.eventBus.mtls.enabled: true`, it mounts `nats-mtls-server-tls` into auth-callout and sets `AUTH_CALLOUT_MTLS_CA_PATH` automatically:

```yaml
auth-callout:
serviceConfig:
global:
eventBus:
mtls:
ca-path: "/etc/mtls-ca/ca.crt"
enabled: true
```

### NKey
Expand Down
13 changes: 0 additions & 13 deletions local/nats/k8s/local-dev-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,6 @@ auth-callout:
url: "http://172.18.200.1/realms/event-bus/protocol/openid-connect/certs"
issuer: "http://172.18.200.1/realms/event-bus"
audience: "dsx-exchange"
# CA certificate for mTLS client verification
mtls:
ca-path: "/etc/mtls-ca/ca.crt"
observability:
tracing:
enabled: false

# Mount CA certificate from TLS secret for mTLS client verification
extraVolumes:
- name: mtls-ca
secret:
secretName: nats-mtls-server-tls
extraVolumeMounts:
- name: mtls-ca
mountPath: /etc/mtls-ca
readOnly: true
Loading