Skip to content

Commit 24cc2cb

Browse files
fix(chart): mount mTLS CA for auth-callout (#28)
Signed-off-by: Frank Spitulski <fspitulski@nvidia.com>
1 parent f01fe84 commit 24cc2cb

8 files changed

Lines changed: 165 additions & 40 deletions

File tree

auth-callout/deploy/README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,49 @@ serviceConfig:
8888

8989
### mTLS Configuration
9090

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

9393
```yaml
9494
serviceConfig:
9595
mtls:
96-
ca-path: "/etc/ssl/certs/ca.crt"
96+
ca-path: "/etc/mtls-ca/ca.crt"
97+
98+
extraVolumeMounts:
99+
- name: mtls-ca
100+
mountPath: /etc/mtls-ca
101+
readOnly: true
102+
103+
extraVolumes:
104+
- name: mtls-ca
105+
secret:
106+
secretName: my-mtls-ca
107+
items:
108+
- key: ca.crt
109+
path: ca.crt
110+
```
111+
112+
Parent charts can use templated snippets when the mount depends on values
113+
visible to this subchart, such as `global`:
114+
115+
```yaml
116+
extraVolumeMountTemplates:
117+
- |
118+
{{- if .Values.global.eventBus.mtls.enabled }}
119+
- name: mtls-ca
120+
mountPath: /etc/mtls-ca
121+
readOnly: true
122+
{{- end }}
123+
124+
extraVolumeTemplates:
125+
- |
126+
{{- if .Values.global.eventBus.mtls.enabled }}
127+
- name: mtls-ca
128+
secret:
129+
secretName: my-mtls-ca
130+
items:
131+
- key: ca.crt
132+
path: ca.crt
133+
{{- end }}
97134
```
98135

99136
## Permissions Configuration

auth-callout/deploy/templates/deployment.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ spec:
116116
{{- with .Values.extraVolumeMounts }}
117117
{{- toYaml . | nindent 12 }}
118118
{{- end }}
119+
{{- range $template := .Values.extraVolumeMountTemplates }}
120+
{{- $rendered := tpl $template $ | trim }}
121+
{{- if $rendered }}
122+
{{ $rendered | nindent 12 }}
123+
{{- end }}
124+
{{- end }}
119125

120126
{{- if .Values.healthChecks.livenessProbe.enabled }}
121127
livenessProbe:
@@ -169,3 +175,9 @@ spec:
169175
{{- with .Values.extraVolumes }}
170176
{{- toYaml . | nindent 8 }}
171177
{{- end }}
178+
{{- range $template := .Values.extraVolumeTemplates }}
179+
{{- $rendered := tpl $template $ | trim }}
180+
{{- if $rendered }}
181+
{{ $rendered | nindent 8 }}
182+
{{- end }}
183+
{{- end }}

auth-callout/deploy/values.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ extraVolumes:
5555
# configMap:
5656
# name: my-configmap
5757

58+
# Additional templated volume snippets.
59+
# Use when volume presence depends on values visible to this chart.
60+
extraVolumeTemplates: []
61+
# - |
62+
# - name: generated-volume
63+
# secret:
64+
# secretName: generated-secret
65+
5866
# Additional volume mounts to be added to the container
5967
extraVolumeMounts:
6068
[]
@@ -64,6 +72,14 @@ extraVolumeMounts:
6472
# mountPath: /etc/secrets
6573
# readOnly: true
6674

75+
# Additional templated volume mount snippets.
76+
# Use when mount presence depends on values visible to this chart.
77+
extraVolumeMountTemplates: []
78+
# - |
79+
# - name: generated-volume
80+
# mountPath: /etc/generated
81+
# readOnly: true
82+
6783
podAnnotations: {}
6884
podLabels: {}
6985

auth-callout/src/internal/auth/auth_test.go

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import (
77
"context"
88
"crypto/rand"
99
"crypto/rsa"
10+
"crypto/x509"
11+
"crypto/x509/pkix"
1012
"encoding/json"
13+
"encoding/pem"
14+
"math/big"
1115
"net/http"
1216
"net/http/httptest"
1317
"os"
@@ -476,36 +480,31 @@ func TestOAuth2RequiredScope(t *testing.T) {
476480

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

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

487-
// Initialize mTLS authenticator without CA (for testing)
488-
mtlsAuth, err := NewMTLSAuthenticator(nil, pm, testLogger(), testServiceName)
489-
require.NoError(t, err)
490+
caPEM, caKey := createTestCA(t)
491+
clientCertPEM := createClientCert(t, "device1", caPEM, caKey)
490492

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

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

504-
if profile == nil {
505-
t.Error("Expected non-nil profile")
506-
}
502+
otherCAPEM, otherCAKey := createTestCA(t)
503+
untrustedCertPEM := createClientCert(t, "device1", otherCAPEM, otherCAKey)
507504

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

511510
// TestNKeyAuthentication tests NKey authentication
@@ -768,3 +767,52 @@ func createTestPermissionsFile(t *testing.T) string {
768767
tmpFile.Close()
769768
return tmpFile.Name()
770769
}
770+
771+
func createTestCA(t *testing.T) ([]byte, *rsa.PrivateKey) {
772+
t.Helper()
773+
774+
key, err := rsa.GenerateKey(rand.Reader, 2048)
775+
require.NoError(t, err)
776+
777+
template := &x509.Certificate{
778+
SerialNumber: big.NewInt(time.Now().UnixNano()),
779+
Subject: pkix.Name{CommonName: "test-ca"},
780+
NotBefore: time.Now().Add(-time.Minute),
781+
NotAfter: time.Now().Add(time.Hour),
782+
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
783+
BasicConstraintsValid: true,
784+
IsCA: true,
785+
}
786+
787+
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
788+
require.NoError(t, err)
789+
790+
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), key
791+
}
792+
793+
func createClientCert(t *testing.T, commonName string, caPEM []byte, caKey *rsa.PrivateKey) string {
794+
t.Helper()
795+
796+
block, _ := pem.Decode(caPEM)
797+
require.NotNil(t, block)
798+
799+
caCert, err := x509.ParseCertificate(block.Bytes)
800+
require.NoError(t, err)
801+
802+
key, err := rsa.GenerateKey(rand.Reader, 2048)
803+
require.NoError(t, err)
804+
805+
template := &x509.Certificate{
806+
SerialNumber: big.NewInt(time.Now().UnixNano()),
807+
Subject: pkix.Name{CommonName: commonName},
808+
NotBefore: time.Now().Add(-time.Minute),
809+
NotAfter: time.Now().Add(time.Hour),
810+
KeyUsage: x509.KeyUsageDigitalSignature,
811+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
812+
}
813+
814+
der, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
815+
require.NoError(t, err)
816+
817+
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
818+
}

deploy/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,16 @@ global:
371371
enabled: true # default
372372
```
373373

374+
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.
375+
374376
Set `global.eventBus.mtls.enabled: false` to disable the mTLS NATS cluster. When disabled:
375377

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

383386
## Subchart Configuration
@@ -551,8 +554,6 @@ auth-callout:
551554
url: "https://keycloak.example.com/realms/event-bus/protocol/openid-connect/certs"
552555
issuer: "https://keycloak.example.com/realms/event-bus"
553556
audience: "dsx-exchange"
554-
mtls:
555-
ca-path: "/etc/mtls-ca/ca.crt"
556557
```
557558

558559
### CSC Cluster

deploy/nats-event-bus/values.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,30 @@ auth-callout:
525525
extraEnvTemplates:
526526
- |
527527
{{ include "nats-event-bus.authCalloutEventBusEnv" . }}
528+
{{- if .Values.global.eventBus.mtls.enabled }}
529+
- name: AUTH_CALLOUT_MTLS_CA_PATH
530+
value: "/etc/mtls-ca/ca.crt"
531+
{{- end }}
532+
533+
# Umbrella-owned mTLS endpoint: wire auth-callout CA only when enabled.
534+
extraVolumeMountTemplates:
535+
- |
536+
{{- if .Values.global.eventBus.mtls.enabled }}
537+
- name: mtls-ca
538+
mountPath: /etc/mtls-ca
539+
readOnly: true
540+
{{- end }}
541+
542+
extraVolumeTemplates:
543+
- |
544+
{{- if .Values.global.eventBus.mtls.enabled }}
545+
- name: mtls-ca
546+
secret:
547+
secretName: nats-mtls-server-tls
548+
items:
549+
- key: ca.crt
550+
path: ca.crt
551+
{{- end }}
528552
529553
# Operator-defined auth-callout env vars.
530554
# The generated permissions use nkey names leaf-cpc-{id} and

docs/authentication.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ auth-callout:
5252
5353
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.
5454
55-
Configure the CA certificate path:
55+
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:
5656

5757
```yaml
58-
auth-callout:
59-
serviceConfig:
58+
global:
59+
eventBus:
6060
mtls:
61-
ca-path: "/etc/mtls-ca/ca.crt"
61+
enabled: true
6262
```
6363

6464
### NKey

local/nats/k8s/local-dev-values.yaml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,6 @@ auth-callout:
2727
url: "http://172.18.200.1/realms/event-bus/protocol/openid-connect/certs"
2828
issuer: "http://172.18.200.1/realms/event-bus"
2929
audience: "dsx-exchange"
30-
# CA certificate for mTLS client verification
31-
mtls:
32-
ca-path: "/etc/mtls-ca/ca.crt"
3330
observability:
3431
tracing:
3532
enabled: false
36-
37-
# Mount CA certificate from TLS secret for mTLS client verification
38-
extraVolumes:
39-
- name: mtls-ca
40-
secret:
41-
secretName: nats-mtls-server-tls
42-
extraVolumeMounts:
43-
- name: mtls-ca
44-
mountPath: /etc/mtls-ca
45-
readOnly: true

0 commit comments

Comments
 (0)