Skip to content
144 linesCodeBlameRaw
1import * as cloudflare from "@pulumi/cloudflare";
2import * as pulumi from "@pulumi/pulumi";
3
4const config = new pulumi.Config();
5const accountId = config.require("cloudflareAccountId");
6const targetUrl = normalizeTarget(
7 config.get("targetUrl") ?? "https://flagon.io",
8);
9const domains = normalizeDomains(config.requireObject<string[]>("domains"));
10
11const redirectListName = "flagon_alternate_domains";
12const redirectList = new cloudflare.List("alternate-domains", {
13 accountId,
14 name: redirectListName,
15 description: "Alternate and commonly misspelled Flagon domains",
16 kind: "redirect",
17 items: domains.map((domain) => ({
18 comment: `Redirect ${domain} and its subdomains to ${targetUrl}`,
19 redirect: {
20 sourceUrl: `${domain}/`,
21 targetUrl,
22 statusCode: 301,
23 includeSubdomains: true,
24 subpathMatching: true,
25 preservePathSuffix: true,
26 preserveQueryString: true,
27 },
28 })),
29});
30
31const zones = domains.map((domain) => {
32 const resourceName = toResourceName(domain);
33 const zone = new cloudflare.Zone(resourceName, {
34 account: { id: accountId },
35 name: domain,
36 type: "full",
37 });
38
39 // Bulk Redirects only see traffic proxied by Cloudflare. These documentation
40 // addresses must never be used as origins; the redirect executes first.
41 for (const [label, name] of [
42 ["apex", domain],
43 ["wildcard", `*.${domain}`],
44 ] as const) {
45 new cloudflare.DnsRecord(`${resourceName}-${label}`, {
46 zoneId: zone.id,
47 name,
48 type: "A",
49 content: "192.0.2.1",
50 ttl: 1,
51 proxied: true,
52 comment: "Proxied placeholder for the account-level Bulk Redirect",
53 });
54 }
55
56 return zone;
57});
58
59new cloudflare.Ruleset(
60 "alternate-domain-redirects",
61 {
62 accountId,
63 name: "Flagon alternate domain redirects",
64 description: "Enables the managed list of alternate Flagon domains",
65 kind: "root",
66 phase: "http_request_redirect",
67 rules: [
68 {
69 ref: "redirect_flagon_alternate_domains",
70 action: "redirect",
71 actionParameters: {
72 fromList: {
73 key: "http.request.full_uri",
74 name: redirectListName,
75 },
76 },
77 description: "Redirect alternate Flagon domains",
78 expression: `http.request.full_uri in $${redirectListName}`,
79 enabled: true,
80 },
81 ],
82 },
83 { dependsOn: [redirectList] },
84);
85
86export const configuredDomains = domains;
87export const target = targetUrl;
88export const zoneNameServers = Object.fromEntries(
89 domains.map((domain, index) => [domain, zones[index]!.nameServers]),
90);
91
92function normalizeDomains(values: string[]): string[] {
93 if (values.length === 0) {
94 throw new pulumi.RunError("The 'domains' configuration must not be empty.");
95 }
96
97 const domains = values.map((value) =>
98 value.trim().toLowerCase().replace(/\.$/, ""),
99 );
100 const invalid = domains.find(
101 (domain) =>
102 !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(
103 domain,
104 ),
105 );
106 if (invalid) {
107 throw new pulumi.RunError(
108 `Invalid domain in 'domains' configuration: ${invalid}`,
109 );
110 }
111
112 const unique = [...new Set(domains)].sort();
113 if (unique.includes(new URL(targetUrl).hostname)) {
114 throw new pulumi.RunError(
115 "The redirect target must not also appear in 'domains'.",
116 );
117 }
118 return unique;
119}
120
121function normalizeTarget(value: string): string {
122 let url: URL;
123 try {
124 url = new URL(value);
125 } catch {
126 throw new pulumi.RunError(`Invalid 'targetUrl' configuration: ${value}`);
127 }
128 if (
129 url.protocol !== "https:" ||
130 url.username ||
131 url.password ||
132 url.search ||
133 url.hash
134 ) {
135 throw new pulumi.RunError(
136 "The 'targetUrl' configuration must be an HTTPS URL without credentials, a query, or a fragment.",
137 );
138 }
139 return url.toString().replace(/\/$/, "");
140}
141
142function toResourceName(domain: string): string {
143 return domain.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
144}