Skip to content

Commit b8166a1

Browse files
committed
Add common ACME DNS provider integrations
1 parent 8820278 commit b8166a1

File tree

14 files changed

+2172
-95
lines changed

14 files changed

+2172
-95
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ instant-acme = "0.8.5"
142142
base64 = "0.22"
143143
x509-parser = "0.16"
144144
sha2 = "0.10.8"
145+
hmac = "0.12.1"
146+
sha1 = "0.10.6"
147+
quick-xml = { version = "0.38.3", features = ["serialize"] }
145148

146149
# ============================================================================
147150
# Database & Storage

landscape-common/src/cert/order.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ pub enum DnsProviderConfig {
6262
Google {
6363
service_account_json: String,
6464
},
65-
Custom {
66-
script_path: String,
67-
},
6865
}
6966

7067
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

landscape-webui/src/components/cert/order/CertOrderEditModal.vue

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,20 @@ const key_type_options = [
117117
{ label: "RSA 4096", value: "rsa4096" },
118118
];
119119
120+
const supported_dns_providers = [
121+
"cloudflare",
122+
"aliyun",
123+
"tencent",
124+
"aws",
125+
"google",
126+
] as const;
127+
120128
const dns_provider_options = [
121129
{ label: t("cert.dns_provider_cloudflare"), value: "cloudflare" },
122-
// Temporarily disabled until backend support is ready:
123-
// { label: t("cert.dns_provider_aliyun"), value: "aliyun" },
124-
// { label: t("cert.dns_provider_tencent"), value: "tencent" },
125-
// { label: t("cert.dns_provider_aws"), value: "aws" },
126-
// { label: t("cert.dns_provider_google"), value: "google" },
127-
// { label: t("cert.dns_provider_custom"), value: "custom" },
130+
{ label: t("cert.dns_provider_aliyun"), value: "aliyun" },
131+
{ label: t("cert.dns_provider_tencent"), value: "tencent" },
132+
{ label: t("cert.dns_provider_aws"), value: "aws" },
133+
{ label: t("cert.dns_provider_google"), value: "google" },
128134
];
129135
130136
const cert_type_options = [
@@ -218,6 +224,46 @@ function setDnsField(fieldName: string, val: string) {
218224
setDnsProvider(dnsProviderKey.value, fields);
219225
}
220226
227+
function requiredDnsFields(provider: string): string[] {
228+
switch (provider) {
229+
case "cloudflare":
230+
return ["api_token"];
231+
case "aliyun":
232+
return ["access_key_id", "access_key_secret"];
233+
case "tencent":
234+
return ["secret_id", "secret_key"];
235+
case "aws":
236+
return ["access_key_id", "secret_access_key", "region"];
237+
case "google":
238+
return ["service_account_json"];
239+
default:
240+
return [];
241+
}
242+
}
243+
244+
function dnsFieldLabel(fieldName: string): string {
245+
switch (fieldName) {
246+
case "api_token":
247+
return "API Token";
248+
case "access_key_id":
249+
return "Access Key ID";
250+
case "access_key_secret":
251+
return "Access Key Secret";
252+
case "secret_id":
253+
return "Secret ID";
254+
case "secret_key":
255+
return "Secret Key";
256+
case "secret_access_key":
257+
return "Secret Access Key";
258+
case "region":
259+
return "Region";
260+
case "service_account_json":
261+
return "Service Account JSON";
262+
default:
263+
return fieldName;
264+
}
265+
}
266+
221267
async function enter() {
222268
accounts.value = await get_cert_accounts();
223269
if (props.rule_id) {
@@ -241,7 +287,7 @@ async function enter() {
241287
});
242288
}
243289
const provider = getDnsProviderKey(getAcmeField("challenge_type"));
244-
if (provider !== "cloudflare") {
290+
if (!supported_dns_providers.includes(provider as any)) {
245291
setDnsProvider("cloudflare", { api_token: "" });
246292
}
247293
}
@@ -263,26 +309,22 @@ function on_dns_provider_change(val: string) {
263309
case "cloudflare":
264310
setDnsProvider("cloudflare", { api_token: "" });
265311
break;
266-
// Temporarily disabled until backend support is ready:
267-
// case "aliyun":
268-
// setDnsProvider("aliyun", { access_key_id: "", access_key_secret: "" });
269-
// break;
270-
// case "tencent":
271-
// setDnsProvider("tencent", { secret_id: "", secret_key: "" });
272-
// break;
273-
// case "aws":
274-
// setDnsProvider("aws", {
275-
// access_key_id: "",
276-
// secret_access_key: "",
277-
// region: "",
278-
// });
279-
// break;
280-
// case "google":
281-
// setDnsProvider("google", { service_account_json: "" });
282-
// break;
283-
// case "custom":
284-
// setDnsProvider("custom", { script_path: "" });
285-
// break;
312+
case "aliyun":
313+
setDnsProvider("aliyun", { access_key_id: "", access_key_secret: "" });
314+
break;
315+
case "tencent":
316+
setDnsProvider("tencent", { secret_id: "", secret_key: "" });
317+
break;
318+
case "aws":
319+
setDnsProvider("aws", {
320+
access_key_id: "",
321+
secret_access_key: "",
322+
region: "",
323+
});
324+
break;
325+
case "google":
326+
setDnsProvider("google", { service_account_json: "" });
327+
break;
286328
default:
287329
setDnsProvider("cloudflare", { api_token: "" });
288330
}
@@ -293,7 +335,7 @@ const domain_rule = {
293335
validator(_: unknown, value: string) {
294336
if (!value) return new Error(t("cert.cert_domains_required"));
295337
if (
296-
!/^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$/.test(
338+
!/^(\*\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/.test(
297339
value,
298340
)
299341
)
@@ -331,6 +373,19 @@ const rules = {
331373
return true;
332374
},
333375
},
376+
"cert_type.challenge_type": {
377+
trigger: ["blur", "change"],
378+
validator() {
379+
if (!is_acme.value || !is_dns.value) return true;
380+
const provider = dnsProviderKey.value;
381+
for (const field of requiredDnsFields(provider)) {
382+
if (!dnsField(field).trim()) {
383+
return new Error(`${dnsFieldLabel(field)} is required`);
384+
}
385+
}
386+
return true;
387+
},
388+
},
334389
};
335390
336391
async function save() {
@@ -453,7 +508,10 @@ async function save() {
453508
</template>
454509

455510
<template v-if="is_dns">
456-
<n-form-item :label="t('cert.dns_provider')">
511+
<n-form-item
512+
:label="t('cert.dns_provider')"
513+
path="cert_type.challenge_type"
514+
>
457515
<n-select
458516
:value="dnsProviderKey"
459517
:options="dns_provider_options"
@@ -473,7 +531,6 @@ async function save() {
473531
</n-form-item>
474532
</template>
475533

476-
<!-- Temporarily disabled until backend support is ready:
477534
<template v-if="dnsProviderKey === 'aliyun'">
478535
<n-form-item label="Access Key ID">
479536
<n-input
@@ -492,9 +549,7 @@ async function save() {
492549
/>
493550
</n-form-item>
494551
</template>
495-
-->
496552

497-
<!-- Temporarily disabled until backend support is ready:
498553
<template v-if="dnsProviderKey === 'tencent'">
499554
<n-form-item label="Secret ID">
500555
<n-input
@@ -511,9 +566,7 @@ async function save() {
511566
/>
512567
</n-form-item>
513568
</template>
514-
-->
515569

516-
<!-- Temporarily disabled until backend support is ready:
517570
<template v-if="dnsProviderKey === 'aws'">
518571
<n-form-item label="Access Key ID">
519572
<n-input
@@ -539,9 +592,7 @@ async function save() {
539592
/>
540593
</n-form-item>
541594
</template>
542-
-->
543595

544-
<!-- Temporarily disabled until backend support is ready:
545596
<template v-if="dnsProviderKey === 'google'">
546597
<n-form-item label="Service Account JSON">
547598
<n-input
@@ -554,19 +605,6 @@ async function save() {
554605
/>
555606
</n-form-item>
556607
</template>
557-
-->
558-
559-
<!-- Temporarily disabled until backend support is ready:
560-
<template v-if="dnsProviderKey === 'custom'">
561-
<n-form-item label="Script Path">
562-
<n-input
563-
:value="dnsField('script_path')"
564-
@update:value="(v: string) => setDnsField('script_path', v)"
565-
placeholder="/path/to/script.sh"
566-
/>
567-
</n-form-item>
568-
</template>
569-
-->
570608
</template>
571609

572610
<n-form-item :label="t('cert.acme_auto_renew')">

landscape-webui/src/i18n/en/cert.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export default {
6767
dns_provider_tencent: "Tencent",
6868
dns_provider_aws: "AWS Route53",
6969
dns_provider_google: "Google Cloud",
70-
dns_provider_custom: "Custom Script",
7170

7271
// Status
7372
status_unregistered: "Unregistered",

landscape-webui/src/i18n/zh/cert.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export default {
6767
dns_provider_tencent: "腾讯云",
6868
dns_provider_aws: "AWS Route53",
6969
dns_provider_google: "Google Cloud",
70-
dns_provider_custom: "自定义脚本",
7170

7271
// Status
7372
status_unregistered: "未注册",

landscape/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ rustls-pki-types = { workspace = true }
3030
pem = { workspace = true }
3131
instant-acme = { workspace = true }
3232
base64 = { workspace = true }
33+
jsonwebtoken = { workspace = true }
3334
x509-parser = { workspace = true }
35+
hmac = { workspace = true }
36+
sha1 = { workspace = true }
37+
quick-xml = { workspace = true }
3438

3539
rtnetlink = { workspace = true }
3640
netlink-sys = { workspace = true }
@@ -86,3 +90,6 @@ default = []
8690
polars = ["dep:polars"]
8791
metric-duckdb = ["dep:duckdb", "dep:r2d2", "dep:dashmap"]
8892
duckdb-bundled = ["metric-duckdb", "duckdb/bundled"]
93+
94+
[dev-dependencies]
95+
axum = { workspace = true }

0 commit comments

Comments
 (0)