Feat v0.6.7 native mTLS (#42)
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m44s
CI/CD / test-sqlite (pull_request) Successful in 2m50s
CI/CD / build-and-deploy (pull_request) Successful in 1m4s
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m44s
CI/CD / test-sqlite (pull_request) Successful in 2m50s
CI/CD / build-and-deploy (pull_request) Successful in 1m4s
End-to-end mutual TLS without a reverse proxy. Go binary terminates TLS itself via ListenAndServeTLS. TLS_MODE config (none/server/mtls) is independent of AUTH_MODE. - MTLSNativeProvider reads PeerCertificates directly (no header trust) - Shared helpers extracted to mtls_helpers.go (ParseDN, FingerprintCert) - MTLSProvider renamed to MTLSProxyProvider for clarity - BuildPeerTLSConfig for future node-to-node mTLS - switchboard-ca.sh: CA init, issue-node, issue-user (ECDSA P-256) - 12 new tests (unit + TLS integration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
128
server/auth/mtls_proxy_test.go
Normal file
128
server/auth/mtls_proxy_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"switchboard-core/store"
|
||||
)
|
||||
|
||||
func TestParseDN(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dn string
|
||||
expect map[string]string
|
||||
}{
|
||||
{
|
||||
name: "standard cert DN",
|
||||
dn: "CN=Jeff Smith,O=Acme Corp,OU=Engineering",
|
||||
expect: map[string]string{
|
||||
"CN": "Jeff Smith",
|
||||
"O": "Acme Corp",
|
||||
"OU": "Engineering",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with email",
|
||||
dn: "CN=Jane Doe,emailAddress=jane@acme.com,O=Acme Corp",
|
||||
expect: map[string]string{
|
||||
"CN": "Jane Doe",
|
||||
"emailAddress": "jane@acme.com",
|
||||
"O": "Acme Corp",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "spaces around equals",
|
||||
dn: "CN = Test User , O = Test Org",
|
||||
expect: map[string]string{
|
||||
"CN": "Test User",
|
||||
"O": "Test Org",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single field",
|
||||
dn: "CN=Solo",
|
||||
expect: map[string]string{
|
||||
"CN": "Solo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
dn: "",
|
||||
expect: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "no equals sign",
|
||||
dn: "garbage,data",
|
||||
expect: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "country and state",
|
||||
dn: "CN=Server,C=US,ST=Maryland,L=Laurel,O=DoD",
|
||||
expect: map[string]string{
|
||||
"CN": "Server",
|
||||
"C": "US",
|
||||
"ST": "Maryland",
|
||||
"L": "Laurel",
|
||||
"O": "DoD",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ParseDN(tt.dn)
|
||||
for k, want := range tt.expect {
|
||||
got := result[k]
|
||||
if got != want {
|
||||
t.Errorf("ParseDN(%q)[%q] = %q, want %q", tt.dn, k, got, want)
|
||||
}
|
||||
}
|
||||
if len(result) != len(tt.expect) {
|
||||
t.Errorf("ParseDN(%q) returned %d fields, want %d", tt.dn, len(result), len(tt.expect))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSConfig_Defaults(t *testing.T) {
|
||||
p := NewMTLSProxyProvider(MTLSProxyConfig{})
|
||||
|
||||
if p.cfg.HeaderDN != "X-SSL-Client-DN" {
|
||||
t.Errorf("HeaderDN = %q, want X-SSL-Client-DN", p.cfg.HeaderDN)
|
||||
}
|
||||
if p.cfg.HeaderVerify != "X-SSL-Client-Verify" {
|
||||
t.Errorf("HeaderVerify = %q, want X-SSL-Client-Verify", p.cfg.HeaderVerify)
|
||||
}
|
||||
if p.cfg.HeaderFingerprint != "X-SSL-Client-Fingerprint" {
|
||||
t.Errorf("HeaderFingerprint = %q, want X-SSL-Client-Fingerprint", p.cfg.HeaderFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSConfig_Custom(t *testing.T) {
|
||||
p := NewMTLSProxyProvider(MTLSProxyConfig{
|
||||
HeaderDN: "X-Client-Cert-DN",
|
||||
HeaderVerify: "X-Client-Cert-Verify",
|
||||
})
|
||||
|
||||
if p.cfg.HeaderDN != "X-Client-Cert-DN" {
|
||||
t.Errorf("HeaderDN = %q, want X-Client-Cert-DN", p.cfg.HeaderDN)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSProvider_Mode(t *testing.T) {
|
||||
p := NewMTLSProxyProvider(MTLSProxyConfig{})
|
||||
if p.Mode() != ModeMTLS {
|
||||
t.Errorf("Mode() = %q, want %q", p.Mode(), ModeMTLS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSProvider_NoRegistration(t *testing.T) {
|
||||
p := NewMTLSProxyProvider(MTLSProxyConfig{})
|
||||
if p.SupportsRegistration() {
|
||||
t.Error("mTLS should not support registration")
|
||||
}
|
||||
_, err := p.Register(nil, store.Stores{})
|
||||
if err != ErrNotSupported {
|
||||
t.Errorf("Register() = %v, want ErrNotSupported", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user