mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-19 14:12:52 +00:00
183 lines
6.2 KiB
TypeScript
183 lines
6.2 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
import {
|
|
GenericContainer,
|
|
type StartedTestContainer,
|
|
Network,
|
|
Wait,
|
|
} from "testcontainers";
|
|
import type { StartedNetwork } from "testcontainers";
|
|
import * as fs from "fs";
|
|
import * as os from "os";
|
|
import * as path from "path";
|
|
import { generateCertificates } from "./utils/tls.ts";
|
|
|
|
const TEST_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
const LOGIN_APP_DIR = path.join(TEST_DIR, "../..");
|
|
|
|
const SMOCKER_MOCK_PORT = 443;
|
|
const SMOCKER_ADMIN_PORT = 8081;
|
|
|
|
/**
|
|
* Integration tests for custom CA certificate support in the login application.
|
|
*
|
|
* These tests verify that the Dockerfile correctly configures Node.js to trust
|
|
* custom Certificate Authorities via SSL_CERT_FILE, SSL_CERT_DIR, and the
|
|
* load-ssl-cert-dir.cjs preload script.
|
|
*
|
|
* OpenSSL (used by Node with --use-openssl-ca) only reads certs from
|
|
* SSL_CERT_DIR when they have hashed filenames (e.g. "9d66eef0.0"). Go's
|
|
* crypto/x509 reads any file. The preload script bridges this gap so that
|
|
* operators get the same behaviour with both the Go backend and the login
|
|
* container.
|
|
*
|
|
* We use Smocker as a TLS server whose certificate is signed by a test CA
|
|
* that is NOT in the system trust store. Each test block configures the
|
|
* login container differently and asserts whether the TLS handshake to
|
|
* Smocker succeeds or fails — verifying actual trust-store behaviour, not
|
|
* just environment variable values.
|
|
*/
|
|
const LOGIN_IMAGE_TAG = `zitadel-login-test:${process.env.GITHUB_SHA || "local"}`;
|
|
|
|
async function testTlsConnection(
|
|
container: StartedTestContainer,
|
|
url: string,
|
|
): Promise<{ exitCode: number; output: string }> {
|
|
return container.exec([
|
|
"node",
|
|
"-e",
|
|
`fetch('${url}').then(() => process.exit(0)).catch(() => process.exit(1))`,
|
|
]);
|
|
}
|
|
|
|
describe("Custom CA Certificate Integration", () => {
|
|
let network: StartedNetwork;
|
|
let mockServer: StartedTestContainer;
|
|
let certsDir: string;
|
|
|
|
beforeAll(async () => {
|
|
certsDir = fs.mkdtempSync(path.join(os.tmpdir(), "ca-test-certs-"));
|
|
|
|
const certs = generateCertificates();
|
|
fs.writeFileSync(path.join(certsDir, "ca.crt"), certs.ca.cert);
|
|
fs.writeFileSync(path.join(certsDir, "server.key"), certs.server.key);
|
|
fs.writeFileSync(path.join(certsDir, "server.crt"), certs.server.cert);
|
|
|
|
network = await new Network().start();
|
|
|
|
mockServer = await new GenericContainer("ghcr.io/smocker-dev/smocker")
|
|
.withNetwork(network)
|
|
.withNetworkAliases("mock-zitadel")
|
|
.withBindMounts([{ source: certsDir, target: "/certs", mode: "ro" }])
|
|
.withEnvironment({
|
|
SMOCKER_MOCK_SERVER_LISTEN_PORT: "443",
|
|
SMOCKER_CONFIG_LISTEN_PORT: "8081",
|
|
SMOCKER_TLS_ENABLE: "true",
|
|
SMOCKER_TLS_CERT_FILE: "/certs/server.crt",
|
|
SMOCKER_TLS_PRIVATE_KEY_FILE: "/certs/server.key",
|
|
})
|
|
.withExposedPorts(SMOCKER_MOCK_PORT, SMOCKER_ADMIN_PORT)
|
|
.withStartupTimeout(120_000)
|
|
.withWaitStrategy(Wait.forLogMessage(/Starting mock server/))
|
|
.start();
|
|
|
|
try {
|
|
await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(LOGIN_IMAGE_TAG);
|
|
} catch (err) {
|
|
throw new Error(`Failed to build login Docker image: ${err instanceof Error ? err.message : err}`);
|
|
}
|
|
}, 180_000);
|
|
|
|
afterAll(async () => {
|
|
await mockServer?.stop().catch(() => {});
|
|
await network?.stop().catch(() => {});
|
|
});
|
|
|
|
describe("when custom CA is provided via SSL_CERT_FILE", () => {
|
|
let container: StartedTestContainer;
|
|
|
|
beforeAll(async () => {
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withEnvironment({
|
|
SSL_CERT_FILE: "/etc/ssl/certs/custom-ca.crt",
|
|
})
|
|
.withCopyFilesToContainer([
|
|
{
|
|
source: path.join(certsDir, "ca.crt"),
|
|
target: "/etc/ssl/certs/custom-ca.crt",
|
|
},
|
|
])
|
|
.withStartupTimeout(180_000)
|
|
.withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000))
|
|
.start();
|
|
}, 180_000);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
});
|
|
|
|
it("can establish TLS connection to server with custom CA", async () => {
|
|
const result = await testTlsConnection(container, "https://mock-zitadel/");
|
|
expect(result.exitCode).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("when custom CA is provided via SSL_CERT_DIR (non-hashed filename)", () => {
|
|
let container: StartedTestContainer;
|
|
let customCaDir: string;
|
|
|
|
beforeAll(async () => {
|
|
customCaDir = fs.mkdtempSync(path.join(os.tmpdir(), "ca-dir-test-"));
|
|
fs.writeFileSync(path.join(customCaDir, "corp-root.crt"), fs.readFileSync(path.join(certsDir, "ca.crt")));
|
|
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withEnvironment({
|
|
SSL_CERT_DIR: "/custom-certs",
|
|
})
|
|
.withCopyFilesToContainer([
|
|
{
|
|
source: path.join(customCaDir, "corp-root.crt"),
|
|
target: "/custom-certs/corp-root.crt",
|
|
},
|
|
])
|
|
.withStartupTimeout(180_000)
|
|
.withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000))
|
|
.start();
|
|
}, 180_000);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
});
|
|
|
|
it("can establish TLS connection with non-hashed cert in SSL_CERT_DIR", async () => {
|
|
const result = await testTlsConnection(container, "https://mock-zitadel/");
|
|
expect(result.exitCode).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("when custom CA certificate is not provided", () => {
|
|
let container: StartedTestContainer;
|
|
|
|
beforeAll(async () => {
|
|
container = await new GenericContainer(LOGIN_IMAGE_TAG)
|
|
.withNetwork(network)
|
|
.withExposedPorts(3000)
|
|
.withStartupTimeout(180_000)
|
|
.withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000))
|
|
.start();
|
|
}, 180_000);
|
|
|
|
afterAll(async () => {
|
|
await container?.stop().catch(() => {});
|
|
});
|
|
|
|
it("cannot establish TLS connection without custom CA", async () => {
|
|
const result = await testTlsConnection(container, "https://mock-zitadel/");
|
|
expect(result.exitCode).not.toBe(0);
|
|
});
|
|
});
|
|
});
|