feat(certificates): enhance service certificate management and monitoring
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 8s
quality / changes (push) Successful in 10s
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m8s
quality / api (push) Successful in 1m9s
CD / quality (push) Successful in 2m31s
CD / publish (push) Successful in 1m35s

- Added new endpoints for listing and checking service certificates, improving visibility into SSL status.
- Integrated certificate monitoring options into service binding updates, allowing for flexible SSL management.
- Updated the service detail grid to include SSL monitoring controls, enhancing user interaction with certificate settings.
- Refactored related components and schemas to support the new certificate features, ensuring consistency across the application.
- Improved test coverage for certificate functionalities, validating the new features and ensuring reliability.
This commit is contained in:
Denozordec
2026-08-20 00:32:43 +07:00
parent 69119a08a4
commit a458465153
20 changed files with 760 additions and 274 deletions
+186 -20
View File
@@ -208,7 +208,7 @@ describe("certificates", () => {
await testApp.close();
});
it("required apex is monitored without bindings", async () => {
it("required binding is monitored without TLS health gate", async () => {
const testApp = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
@@ -221,10 +221,18 @@ describe("certificates", () => {
"required.example.com",
"cf-zone-req",
);
repos.updateDomain(testApp.db, domain.id, {
group_id: null,
status: "active",
const service = repos.createService(testApp.db, "Req", "req");
repos.setServiceEnabled(testApp.db, service.id, true);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"@",
null,
);
repos.updateBindingLbConfig(testApp.db, binding.id, {
cert_monitoring: CERT_MONITOR_REQUIRED,
health_check_enabled: false,
});
vi.spyOn(certificateService, "checkHostname").mockResolvedValue({
@@ -247,7 +255,7 @@ describe("certificates", () => {
await testApp.close();
});
it("skipped apex removes stale certificate on check", async () => {
it("skipped binding removes stale certificate on check", async () => {
const testApp = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
@@ -260,6 +268,20 @@ describe("certificates", () => {
"skipped.example.com",
"cf-zone-skip",
);
const service = repos.createService(testApp.db, "Skip", "skip");
repos.setServiceEnabled(testApp.db, service.id, true);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"@",
null,
);
repos.updateBindingLbConfig(testApp.db, binding.id, {
cert_monitoring: CERT_MONITOR_SKIPPED,
health_check_enabled: true,
health_check_verify_tls: true,
});
repos.upsertCertificateCheck(
testApp.db,
domain.id,
@@ -268,12 +290,8 @@ describe("certificates", () => {
null,
CERT_ERROR,
"stale",
service.id,
);
repos.updateDomain(testApp.db, domain.id, {
group_id: null,
status: "active",
cert_monitoring: CERT_MONITOR_SKIPPED,
});
vi.spyOn(certificateService, "checkHostname").mockResolvedValue({
expiresAt: null,
@@ -305,9 +323,16 @@ describe("certificates", () => {
"broken.example.com",
"cf-zone-broken",
);
repos.updateDomain(testApp.db, domain.id, {
group_id: null,
status: "active",
const service = repos.createService(testApp.db, "Broken", "broken");
repos.setServiceEnabled(testApp.db, service.id, true);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"@",
null,
);
repos.updateBindingLbConfig(testApp.db, binding.id, {
cert_monitoring: CERT_MONITOR_REQUIRED,
});
@@ -424,7 +449,7 @@ describe("certificates", () => {
});
const certs = repos.listCertificates(testApp.db);
expect(certs.some((c) => c.hostname === "lb.ok.example.com")).toBe(true);
expect(certs.some((c) => c.hostname === "lb.ok.example.com")).toBe(false);
expect(certs.some((c) => c.hostname === "edge.ok.example.com")).toBe(true);
await testApp.close();
@@ -443,12 +468,7 @@ describe("certificates", () => {
"force.example.com",
"cf-zone-force",
);
repos.updateDomain(testApp.db, domain.id, {
group_id: null,
status: "active",
cert_monitoring: CERT_MONITOR_REQUIRED,
});
repos.createServiceGroup(
const group = repos.createServiceGroup(
testApp.db,
"Proxy",
"vpn",
@@ -459,6 +479,19 @@ describe("certificates", () => {
health_check_verify_tls: false,
},
);
const service = repos.createService(testApp.db, "Force", "force");
repos.setServiceEnabled(testApp.db, service.id, true);
repos.setServiceGroup(testApp.db, service.id, group.id);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"@",
null,
);
repos.updateBindingLbConfig(testApp.db, binding.id, {
cert_monitoring: CERT_MONITOR_REQUIRED,
});
vi.spyOn(certificateService, "checkHostname").mockResolvedValue({
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
@@ -540,4 +573,137 @@ describe("certificates", () => {
await testApp.close();
});
it("GET /services/:id/certificates lists binding FQDNs", async () => {
const testApp = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(testApp);
const domain = repos.createDomain(
testApp.db,
null,
"svc.example.com",
"cf-zone-svc",
);
const service = repos.createService(testApp.db, "Api", "api");
repos.setServiceEnabled(testApp.db, service.id, true);
repos.insertBinding(testApp.db, domain.id, service.id, "www", null);
const res = await testApp.inject({
method: "GET",
url: `/api/v1/services/${service.id}/certificates`,
headers,
});
expect(res.statusCode).toBe(200);
const rows = res.json() as Array<{
hostname: string;
cert_monitoring: string;
status: string;
}>;
expect(rows).toHaveLength(1);
expect(rows[0]?.hostname).toBe("www.svc.example.com");
expect(rows[0]?.cert_monitoring).toBe("auto");
expect(rows[0]?.status).toBe("unknown");
await testApp.close();
});
it("PATCH /service-bindings/:id updates cert_monitoring", async () => {
const testApp = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(testApp);
const domain = repos.createDomain(
testApp.db,
null,
"patch.example.com",
"cf-zone-patch",
);
const service = repos.createService(testApp.db, "Patch", "patch");
repos.setServiceEnabled(testApp.db, service.id, true);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"api",
null,
);
const res = await testApp.inject({
method: "PATCH",
url: `/api/v1/service-bindings/${binding.id}`,
headers,
payload: { cert_monitoring: CERT_MONITOR_REQUIRED },
});
expect(res.statusCode).toBe(200);
expect((res.json() as { cert_monitoring: string }).cert_monitoring).toBe(
CERT_MONITOR_REQUIRED,
);
expect(repos.getBinding(testApp.db, binding.id).cert_monitoring).toBe(
CERT_MONITOR_REQUIRED,
);
await testApp.close();
});
it("POST /services/:id/certificates/check only checks that service", async () => {
const testApp = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(testApp);
const domain = repos.createDomain(
testApp.db,
null,
"check.example.com",
"cf-zone-check",
);
const service = repos.createService(testApp.db, "One", "one");
const other = repos.createService(testApp.db, "Two", "two");
repos.setServiceEnabled(testApp.db, service.id, true);
repos.setServiceEnabled(testApp.db, other.id, true);
const binding = repos.insertBinding(
testApp.db,
domain.id,
service.id,
"one",
null,
);
const otherBinding = repos.insertBinding(
testApp.db,
domain.id,
other.id,
"two",
null,
);
repos.updateBindingLbConfig(testApp.db, binding.id, {
cert_monitoring: CERT_MONITOR_REQUIRED,
});
repos.updateBindingLbConfig(testApp.db, otherBinding.id, {
cert_monitoring: CERT_MONITOR_REQUIRED,
});
vi.spyOn(certificateService, "checkHostname").mockResolvedValue({
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
error: null,
});
const res = await testApp.inject({
method: "POST",
url: `/api/v1/services/${service.id}/certificates/check`,
headers,
});
expect(res.statusCode).toBe(200);
expect((res.json() as { checked: number }).checked).toBe(1);
const certs = repos.listCertificates(testApp.db);
expect(certs.some((c) => c.hostname === "one.check.example.com")).toBe(true);
expect(certs.some((c) => c.hostname === "two.check.example.com")).toBe(false);
await testApp.close();
});
});