diff --git a/apps/api/dist/server.js b/apps/api/dist/server.js index a3f82ac..7a34ac8 100644 --- a/apps/api/dist/server.js +++ b/apps/api/dist/server.js @@ -1847,6 +1847,7 @@ async function createGroup2(db, cf, body) { async function updateGroup2(db, cf, id, body) { const groupType = body.type?.trim() || "custom"; const previous = repos6.getServiceGroup(db, id); + const name = body.name ?? previous.name; const oldDomain = previous.domain?.trim(); if (oldDomain) { await cleanupStaleGroupFqdnBindings(db, cf, id, oldDomain); @@ -1856,7 +1857,7 @@ async function updateGroup2(db, cf, id, body) { let group = repos6.updateServiceGroup( db, id, - body.name, + name, groupType, body.icon ?? null, domain, @@ -2562,6 +2563,7 @@ import { healthStatusQuerySchema } from "@cfdm/shared"; // src/services/health-check-service.ts import { connect as connect2 } from "net"; +import { Agent, fetch as undiciFetch } from "undici"; import { repos as repos10 } from "@cfdm/db"; function tcpProbe(ip, port, timeoutMs) { return new Promise((resolve4) => { @@ -2603,14 +2605,24 @@ function tcpProbe(ip, port, timeoutMs) { async function httpProbe(ip, target, timeoutMs) { const started = Date.now(); const path = target.path?.trim() || "/"; - const url = `http://${ip}${path.startsWith("/") ? path : `/${path}`}`; - const hostHeader = target.hostname || ip; + const pathWithSlash = path.startsWith("/") ? path : `/${path}`; + const port = target.port ?? 80; + const useTls = port === 443; + const urlHost = useTls ? target.hostname || ip : ip; + const url = `${useTls ? "https" : "http"}://${urlHost}${pathWithSlash}`; + const dispatcher = useTls && target.hostname ? new Agent({ + connect: { + servername: target.hostname, + rejectUnauthorized: false + } + }) : void 0; try { - const response = await fetch(url, { + const response = await undiciFetch(url, { method: "GET", - headers: { Host: hostHeader }, + headers: { Host: target.hostname || ip }, signal: AbortSignal.timeout(timeoutMs), - redirect: "manual" + redirect: "manual", + dispatcher }); const latency = Date.now() - started; if (target.expected_status != null) { diff --git a/packages/db/dist/index.js b/packages/db/dist/index.js index cb7c8c7..8ccfd15 100644 --- a/packages/db/dist/index.js +++ b/packages/db/dist/index.js @@ -1126,9 +1126,10 @@ function deleteIpHealthStatusForIp(db, scope, refId, ip) { ).run(); } function listHealthCheckTargets(db) { + const fqdnExpr = sql2`CASE WHEN sb.hostname = '@' OR sb.hostname IS NULL THEN d.zone_name ELSE sb.hostname || '.' || d.zone_name END`; const bindingTargets = db.all(sql2` SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sb.health_check_type AS type, sb.health_check_port AS port, sb.health_check_path AS path, @@ -1136,6 +1137,7 @@ function listHealthCheckTargets(db) { sb.health_check_timeout_ms AS timeout_ms FROM service_binding_ips sbi JOIN service_bindings sb ON sb.id = sbi.binding_id + JOIN domains d ON d.id = sb.domain_id WHERE sb.health_check_enabled = 1 `); const groupTargets = db.all(sql2` @@ -1156,7 +1158,7 @@ function listHealthCheckTargets(db) { `); const groupInheritedBindingTargets = db.all(sql2` SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sg.health_check_type AS type, sg.health_check_port AS port, sg.health_check_path AS path, @@ -1164,6 +1166,7 @@ function listHealthCheckTargets(db) { sg.health_check_timeout_ms AS timeout_ms FROM service_binding_ips sbi JOIN service_bindings sb ON sb.id = sbi.binding_id + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id JOIN service_groups sg ON sg.id = s.service_group_id WHERE sg.health_check_enabled = 1 @@ -1174,13 +1177,14 @@ function listHealthCheckTargets(db) { `); const cnameBindingTargets = db.all(sql2` SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sb.health_check_type AS type, sb.health_check_port AS port, sb.health_check_path AS path, sb.health_check_expected_status AS expected_status, sb.health_check_timeout_ms AS timeout_ms FROM service_bindings sb + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id WHERE sb.health_check_enabled = 1 AND sb.cname_target IS NOT NULL @@ -1189,13 +1193,14 @@ function listHealthCheckTargets(db) { `); const groupInheritedCnameBindingTargets = db.all(sql2` SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sg.health_check_type AS type, sg.health_check_port AS port, sg.health_check_path AS path, sg.health_check_expected_status AS expected_status, sg.health_check_timeout_ms AS timeout_ms FROM service_bindings sb + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id JOIN service_groups sg ON sg.id = s.service_group_id WHERE sg.health_check_enabled = 1 diff --git a/packages/db/src/repos.ts b/packages/db/src/repos.ts index 938eb29..b3a7dbf 100644 --- a/packages/db/src/repos.ts +++ b/packages/db/src/repos.ts @@ -1486,9 +1486,14 @@ export function deleteIpHealthStatusForIp( // --- Health Check Targets --- export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { + // FQDN for a binding: "@" => zone_name, else ".". + // Used as SNI / Host header for HTTP(S) probes — the raw `sb.hostname` is just the record name + // (e.g. "de" or "@"), which would break TLS SNI (ssl alert 112 "unrecognized name"). + const fqdnExpr = sql`CASE WHEN sb.hostname = '@' OR sb.hostname IS NULL THEN d.zone_name ELSE sb.hostname || '.' || d.zone_name END`; + const bindingTargets = db.all(sql` SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sb.health_check_type AS type, sb.health_check_port AS port, sb.health_check_path AS path, @@ -1496,6 +1501,7 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { sb.health_check_timeout_ms AS timeout_ms FROM service_binding_ips sbi JOIN service_bindings sb ON sb.id = sbi.binding_id + JOIN domains d ON d.id = sb.domain_id WHERE sb.health_check_enabled = 1 `); @@ -1522,7 +1528,7 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { // their own health_check_enabled=1 (covered by bindingTargets above). const groupInheritedBindingTargets = db.all(sql` SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sg.health_check_type AS type, sg.health_check_port AS port, sg.health_check_path AS path, @@ -1530,6 +1536,7 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { sg.health_check_timeout_ms AS timeout_ms FROM service_binding_ips sbi JOIN service_bindings sb ON sb.id = sbi.binding_id + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id JOIN service_groups sg ON sg.id = s.service_group_id WHERE sg.health_check_enabled = 1 @@ -1542,13 +1549,14 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { // CNAME-bindings with their own health_check_enabled: probe the CNAME target host. const cnameBindingTargets = db.all(sql` SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sb.health_check_type AS type, sb.health_check_port AS port, sb.health_check_path AS path, sb.health_check_expected_status AS expected_status, sb.health_check_timeout_ms AS timeout_ms FROM service_bindings sb + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id WHERE sb.health_check_enabled = 1 AND sb.cname_target IS NOT NULL @@ -1560,13 +1568,14 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] { // (inherit group config). Only for bindings without their own health_check_enabled. const groupInheritedCnameBindingTargets = db.all(sql` SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip, - sb.hostname AS hostname, + ${fqdnExpr} AS hostname, sg.health_check_type AS type, sg.health_check_port AS port, sg.health_check_path AS path, sg.health_check_expected_status AS expected_status, sg.health_check_timeout_ms AS timeout_ms FROM service_bindings sb + JOIN domains d ON d.id = sb.domain_id JOIN services s ON s.id = sb.service_id JOIN service_groups sg ON sg.id = s.service_group_id WHERE sg.health_check_enabled = 1