1import * as ACME from "@/ACME";
2import * as Cloudflare from "@/Cloudflare";
3import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
4import { findZoneByName } from "@/Cloudflare/Zone/lookup";
5import * as Test from "@/Test/Alchemy";
6import * as ZeroSsl from "@distilled.cloud/zerossl";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";
10import * as FetchHttpClient from "effect/http/FetchHttpClient";
11
12/**
13 * ZeroSSL has no staging environment, so this runs against production
14 * with External Account Binding credentials minted from the REST access
15 * key. Opt in with `ACME_TEST_ZEROSSL=1` (plus `ZERO_SSL_KEY`).
16 */
17const { test } = Test.make({
18 providers: Layer.mergeAll(ACME.providers(), Cloudflare.providers()),
19});
20
21const enabled =
22 process.env.ACME_TEST_ZEROSSL === "1" &&
23 (process.env.ZERO_SSL_KEY !== undefined ||
24 process.env.ZEROSSL_ACCESS_KEY !== undefined);
25
26const zoneName =
27 process.env.CLOUDFLARE_TEST_DNS_ZONE_NAME ?? "alchemy-test-2.us";
28const NAME = `alchemy-acme-zerossl.${zoneName}`;
29
30const resolveZoneId = Effect.gen(function* () {
31 const { accountId } = yield* yield* CloudflareEnvironment;
32 const zone = yield* findZoneByName({ accountId, name: zoneName });
33 if (!zone) {
34 return yield* Effect.die(new Error(`zone "${zoneName}" not found`));
35 }
36 return zone.id;
37});
38
39const mintEab = ZeroSsl.zerossl
40 .generateEabCredentials({})
41 .pipe(
42 Effect.provide(
43 Layer.mergeAll(ZeroSsl.CredentialsFromEnv, FetchHttpClient.layer),
44 ),
45 );
46
47test.provider.skipIf(!enabled)(
48 "issues from ZeroSSL with EAB credentials over Cloudflare DNS-01",
49 (stack) =>
50 Effect.gen(function* () {
51 const zoneId = yield* resolveZoneId;
52 const eab = yield* mintEab;
53 expect(eab.success).toBe(true);
54 yield* stack.destroy();
55
56 const deployed = yield* stack.deploy(
57 Effect.gen(function* () {
58 const account = yield* ACME.Account("ZeroSSL", {
59 ca: ACME.ZeroSSL,
60 eab: {
61 keyId: eab.eab_kid!,
62 // Already `Redacted` — the SDK marks the field sensitive.
63 hmacKey: eab.eab_hmac_key!,
64 },
65 termsOfServiceAgreed: true,
66 });
67 const cert = yield* ACME.Certificate("ZeroSSLCert", {
68 account,
69 identifiers: [NAME],
70 solver: { type: "Cloudflare.DNS", zoneId },
71 });
72 return { account, cert };
73 }),
74 );
75
76 expect(deployed.account.accountUrl).toContain("acme.zerossl.com");
77 expect(deployed.cert.issuer).toContain("ZeroSSL");
78 const parsed = yield* ACME.parseCertificate(deployed.cert.certificate);
79 expect(parsed.dnsNames).toEqual([NAME]);
80
81 yield* stack.destroy();
82 }),
83 {
84 tags: [
85 "provider:acme",
86 "provider:acme:account",
87 "provider:acme:certificate",
88 "provider:cloudflare",
89 "provider:cloudflare:zone",
90 "provider:zerossl",
91 "live",
92 ],
93 timeout: 300_000,
94 },
95);