Files
caddy-proxy-manager/tests/unit/caddy-mtls-leaf-override.test.ts
akanealw 99819b70ff
Build and Push Docker Images (Trusted) / build-and-push (., docker/caddy/Dockerfile, caddy) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/l4-port-manager/Dockerfile, l4-port-manager) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/web/Dockerfile, web) (push) Has been cancelled
Tests / test (push) Has been cancelled
added caddy-proxy-manager for testing
2026-04-21 22:49:08 +00:00

187 lines
6.1 KiB
TypeScript
Executable File

/**
* Unit tests for the new cert-based mTLS model (leaf override / trusted_client_cert_ids).
*
* Tests buildClientAuthentication with the mTlsDomainLeafOverride parameter
* to ensure the new "trust user X" model works correctly alongside the legacy CA model.
*/
import { describe, it, expect } from 'vitest';
import { buildClientAuthentication } from '../../src/lib/caddy-mtls';
function makeCaPem(label: string): string {
return `-----BEGIN CERTIFICATE-----\n${label}\n-----END CERTIFICATE-----`;
}
function makeCaCertMap(...entries: [number, string][]) {
return new Map(entries.map(([id, label]) => [id, { id, certificatePem: makeCaPem(label) }]));
}
describe('buildClientAuthentication with leaf override (new cert-based model)', () => {
it('uses leaf override PEMs when mTlsDomainLeafOverride is provided', () => {
const mTlsDomainMap = new Map([['app.example.com', [1]]]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
const leafOverride = new Map([['app.example.com', [makeCaPem('USER_CERT_1'), makeCaPem('USER_CERT_2')]]]);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
leafOverride
);
expect(result).not.toBeNull();
expect(result!.mode).toBe('require_and_verify');
expect(result!.trusted_ca_certs).toEqual(['CA_A']);
expect(result!.trusted_leaf_certs).toEqual(['USER_CERT_1', 'USER_CERT_2']);
});
it('ignores the legacy managed/unmanaged CA logic when leaf override is present', () => {
const mTlsDomainMap = new Map([['app.example.com', [1]]]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
// CA 1 is managed with active certs, but leaf override should take precedence
const issuedClientCertMap = new Map([[1, [makeCaPem('OTHER_CERT')]]]);
const cAsWithAnyIssuedCerts = new Set([1]);
const leafOverride = new Map([['app.example.com', [makeCaPem('SPECIFIC_USER')]]]);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
issuedClientCertMap,
cAsWithAnyIssuedCerts,
leafOverride
);
expect(result).not.toBeNull();
// Should only have the override leaf, not OTHER_CERT from the managed CA
expect(result!.trusted_leaf_certs).toEqual(['SPECIFIC_USER']);
expect(result!.trusted_leaf_certs).not.toContain('OTHER_CERT');
});
it('falls back to legacy CA logic when no leaf override exists for domain', () => {
const mTlsDomainMap = new Map([['app.example.com', [1]]]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
// No leaf override for this domain
const leafOverride = new Map([['other.example.com', [makeCaPem('OTHER')]]]);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
leafOverride
);
// Falls back to unmanaged CA: no leaf certs
expect(result).not.toBeNull();
expect(result!.trusted_ca_certs).toEqual(['CA_A']);
expect(result!.trusted_leaf_certs).toBeUndefined();
});
it('includes CAs from multiple domains in leaf override', () => {
const mTlsDomainMap = new Map([
['app.example.com', [1, 2]],
]);
const caCertMap = makeCaCertMap([1, 'CA_A'], [2, 'CA_B']);
const leafOverride = new Map([['app.example.com', [makeCaPem('USER_1')]]]);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
leafOverride
);
expect(result).not.toBeNull();
expect(result!.trusted_ca_certs).toContain('CA_A');
expect(result!.trusted_ca_certs).toContain('CA_B');
expect(result!.trusted_leaf_certs).toEqual(['USER_1']);
});
it('deduplicates leaf PEMs from multiple domains in same group', () => {
const sharedPem = makeCaPem('SHARED_CERT');
const mTlsDomainMap = new Map([
['a.example.com', [1]],
['b.example.com', [1]],
]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
const leafOverride = new Map([
['a.example.com', [sharedPem]],
['b.example.com', [sharedPem]],
]);
const result = buildClientAuthentication(
['a.example.com', 'b.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
leafOverride
);
expect(result).not.toBeNull();
// The Set-based dedup in the function should handle this
// Actually looking at the code, it uses a Set for PEMs
const leafCerts = result!.trusted_leaf_certs as string[];
// pemToBase64Der('SHARED_CERT') appears once since we used a Set
expect(leafCerts.length).toBe(1);
});
it('returns null when leaf override has PEMs but no CA exists in caCertMap', () => {
const mTlsDomainMap = new Map([['app.example.com', [99]]]);
const caCertMap = new Map<number, { id: number; certificatePem: string }>(); // CA 99 not found
const leafOverride = new Map([['app.example.com', [makeCaPem('USER')]]]);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
leafOverride
);
expect(result).toBeNull();
});
it('handles empty leaf override map (same as no override)', () => {
const mTlsDomainMap = new Map([['app.example.com', [1]]]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
new Map()
);
// No override → unmanaged CA logic
expect(result).not.toBeNull();
expect(result!.trusted_ca_certs).toEqual(['CA_A']);
expect(result!.trusted_leaf_certs).toBeUndefined();
});
it('handles undefined leaf override (backward compat)', () => {
const mTlsDomainMap = new Map([['app.example.com', [1]]]);
const caCertMap = makeCaCertMap([1, 'CA_A']);
const result = buildClientAuthentication(
['app.example.com'],
mTlsDomainMap,
caCertMap,
new Map(),
new Set(),
undefined
);
expect(result).not.toBeNull();
expect(result!.trusted_ca_certs).toEqual(['CA_A']);
});
});