198 lines
5.1 KiB
JavaScript
198 lines
5.1 KiB
JavaScript
import { execSync, spawnSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import { CA_CERT, CERTS_DIR, HOST, SERVER_CERT, SERVER_KEY } from './config.mjs';
|
|
|
|
const SQL_CA_DIR = '/var/opt/mssql/security/ca-certificates';
|
|
|
|
export function certHosts(host) {
|
|
const hosts = new Set([host, '127.0.0.1', 'localhost', 's3backup.local']);
|
|
for (const iface of Object.values(os.networkInterfaces())) {
|
|
for (const addr of iface || []) {
|
|
if (addr.family === 'IPv4' && !addr.internal && !addr.address.startsWith('169.254.')) {
|
|
hosts.add(addr.address);
|
|
}
|
|
}
|
|
}
|
|
return [...hosts].sort();
|
|
}
|
|
|
|
export function ensureCerts(host = HOST) {
|
|
fs.mkdirSync(CERTS_DIR, { recursive: true });
|
|
const hosts = certHosts(host);
|
|
const marker = path.join(CERTS_DIR, 'hosts.txt');
|
|
const hostList = hosts.join('\n');
|
|
if (
|
|
fs.existsSync(SERVER_CERT) &&
|
|
fs.existsSync(SERVER_KEY) &&
|
|
fs.existsSync(CA_CERT) &&
|
|
fs.existsSync(marker) &&
|
|
fs.readFileSync(marker, 'utf8') === hostList
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const cnf = `${CERTS_DIR}/openssl.cnf`;
|
|
const altNames = hosts
|
|
.map((h, i) => (/^\d+\./.test(h) ? `IP.${i + 1} = ${h}` : `DNS.${i + 1} = ${h}`))
|
|
.join('\n');
|
|
|
|
fs.writeFileSync(
|
|
cnf,
|
|
`[req]
|
|
distinguished_name = req_distinguished_name
|
|
x509_extensions = v3_req
|
|
prompt = no
|
|
|
|
[req_distinguished_name]
|
|
CN = ${host}
|
|
|
|
[v3_req]
|
|
subjectAltName = @alt_names
|
|
basicConstraints = CA:FALSE
|
|
keyUsage = digitalSignature, keyEncipherment
|
|
extendedKeyUsage = serverAuth
|
|
|
|
[alt_names]
|
|
${altNames}
|
|
`
|
|
);
|
|
|
|
execSync(
|
|
`openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
|
-keyout "${CERTS_DIR}/ca-key.pem" -out "${CA_CERT}" \
|
|
-subj "/CN=JTL S3 Backup CA/O=JTL/C=DE"`,
|
|
{ stdio: 'pipe' }
|
|
);
|
|
|
|
execSync(
|
|
`openssl req -newkey rsa:2048 -nodes \
|
|
-keyout "${SERVER_KEY}" -out "${CERTS_DIR}/server.csr" \
|
|
-config "${cnf}"`,
|
|
{ stdio: 'pipe' }
|
|
);
|
|
|
|
execSync(
|
|
`openssl x509 -req -in "${CERTS_DIR}/server.csr" \
|
|
-CA "${CA_CERT}" -CAkey "${CERTS_DIR}/ca-key.pem" -CAcreateserial \
|
|
-out "${SERVER_CERT}" -days 3650 -extensions v3_req -extfile "${cnf}"`,
|
|
{ stdio: 'pipe' }
|
|
);
|
|
fs.writeFileSync(marker, hostList);
|
|
}
|
|
|
|
export function caTrustStatus(container = process.env.MSSQL_DOCKER_CONTAINER || 'mssql') {
|
|
if (!fs.existsSync(CA_CERT)) {
|
|
return { ok: false, inSync: false, restarted: false };
|
|
}
|
|
const localFp = execSync(`openssl x509 -in "${CA_CERT}" -noout -fingerprint -sha256`, {
|
|
encoding: 'utf8',
|
|
}).trim();
|
|
const remoteFp = spawnSync(
|
|
'docker',
|
|
[
|
|
'exec',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
`test -f ${SQL_CA_DIR}/jtlsrv-s3-ca.pem && openssl x509 -in ${SQL_CA_DIR}/jtlsrv-s3-ca.pem -noout -fingerprint -sha256`,
|
|
],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
const inSync = remoteFp.status === 0 && remoteFp.stdout.trim() === localFp;
|
|
return { ok: remoteFp.status === 0, inSync, restarted: false };
|
|
}
|
|
|
|
export function installCaTrust(container = process.env.MSSQL_DOCKER_CONTAINER || 'mssql') {
|
|
if (!fs.existsSync(CA_CERT)) {
|
|
ensureCerts();
|
|
}
|
|
|
|
const localFp = execSync(`openssl x509 -in "${CA_CERT}" -noout -fingerprint -sha256`, {
|
|
encoding: 'utf8',
|
|
}).trim();
|
|
const remoteFp = spawnSync(
|
|
'docker',
|
|
[
|
|
'exec',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
`test -f ${SQL_CA_DIR}/jtlsrv-s3-ca.pem && openssl x509 -in ${SQL_CA_DIR}/jtlsrv-s3-ca.pem -noout -fingerprint -sha256`,
|
|
],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
if (remoteFp.status === 0 && remoteFp.stdout.trim() === localFp) {
|
|
spawnSync('docker', [
|
|
'exec',
|
|
'-u',
|
|
'root',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
'grep -q s3backup.local /etc/hosts || echo "172.17.0.1 s3backup.local" >> /etc/hosts',
|
|
]);
|
|
return { ok: true, restarted: false };
|
|
}
|
|
|
|
spawnSync('docker', ['exec', '-u', 'root', container, 'mkdir', '-p', SQL_CA_DIR], {
|
|
stdio: 'pipe',
|
|
});
|
|
spawnSync('docker', [
|
|
'exec',
|
|
'-u',
|
|
'root',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
`rm -f ${SQL_CA_DIR}/*.pem ${SQL_CA_DIR}/*.crt`,
|
|
]);
|
|
spawnSync('docker', [
|
|
'exec',
|
|
'-u',
|
|
'root',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
'grep -q s3backup.local /etc/hosts || echo "172.17.0.1 s3backup.local" >> /etc/hosts',
|
|
]);
|
|
try {
|
|
execSync('grep -q s3backup.local /etc/hosts || echo "172.17.0.1 s3backup.local" >> /etc/hosts', {
|
|
stdio: 'pipe',
|
|
});
|
|
} catch {
|
|
// optional on host
|
|
}
|
|
|
|
const copied = spawnSync(
|
|
'docker',
|
|
['cp', CA_CERT, `${container}:${SQL_CA_DIR}/jtlsrv-s3-ca.pem`],
|
|
{ stdio: 'pipe' }
|
|
);
|
|
if (copied.status !== 0) {
|
|
return { ok: false, restarted: false };
|
|
}
|
|
|
|
const perms = spawnSync(
|
|
'docker',
|
|
[
|
|
'exec',
|
|
'-u',
|
|
'root',
|
|
container,
|
|
'bash',
|
|
'-lc',
|
|
`chown mssql:mssql ${SQL_CA_DIR}/jtlsrv-s3-ca.pem && chmod 644 ${SQL_CA_DIR}/jtlsrv-s3-ca.pem`,
|
|
],
|
|
{ stdio: 'pipe' }
|
|
);
|
|
if (perms.status !== 0) {
|
|
return { ok: false, restarted: false };
|
|
}
|
|
|
|
const restarted = spawnSync('docker', ['restart', container], { stdio: 'pipe' });
|
|
return { ok: true, restarted: restarted.status === 0 };
|
|
}
|