mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-04-10 20:53:16 +08:00
Merge pull request #502 from LystranG/feat-acmedns
【新增】支持 acme-dns DNS 验证方式
This commit is contained in:
@@ -3,6 +3,7 @@ package apply
|
|||||||
import (
|
import (
|
||||||
"ALLinSSL/backend/internal/access"
|
"ALLinSSL/backend/internal/access"
|
||||||
"ALLinSSL/backend/internal/cert"
|
"ALLinSSL/backend/internal/cert"
|
||||||
|
"ALLinSSL/backend/internal/cert/apply/lego/acmedns"
|
||||||
"ALLinSSL/backend/internal/cert/apply/lego/bt"
|
"ALLinSSL/backend/internal/cert/apply/lego/bt"
|
||||||
"ALLinSSL/backend/internal/cert/apply/lego/jdcloud"
|
"ALLinSSL/backend/internal/cert/apply/lego/jdcloud"
|
||||||
"ALLinSSL/backend/internal/cert/apply/lego/webhook"
|
"ALLinSSL/backend/internal/cert/apply/lego/webhook"
|
||||||
@@ -242,6 +243,12 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht
|
|||||||
config.SecretKey = creds["secret_key"]
|
config.SecretKey = creds["secret_key"]
|
||||||
config.PropagationTimeout = maxWait
|
config.PropagationTimeout = maxWait
|
||||||
return edgeone.NewDNSProviderConfig(config)
|
return edgeone.NewDNSProviderConfig(config)
|
||||||
|
case "acmedns":
|
||||||
|
config := &acmedns.Config{
|
||||||
|
ServerURL: creds["server_url"],
|
||||||
|
Credentials: creds["credentials"],
|
||||||
|
}
|
||||||
|
return acmedns.NewDNSProviderConfig(config)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
||||||
|
|||||||
125
backend/internal/cert/apply/lego/acmedns/lego.go
Normal file
125
backend/internal/cert/apply/lego/acmedns/lego.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package acmedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
legoacmedns "github.com/go-acme/lego/v4/providers/dns/acmedns"
|
||||||
|
"github.com/nrdcg/goacmedns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ServerURL string `json:"server_url"`
|
||||||
|
Credentials string `json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type acmeDNSUpdater interface {
|
||||||
|
UpdateTXTRecord(ctx context.Context, account goacmedns.Account, value string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type singleAccountProvider struct {
|
||||||
|
account goacmedns.Account
|
||||||
|
client acmeDNSUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ challenge.Provider = (*singleAccountProvider)(nil)
|
||||||
|
|
||||||
|
func NewDNSProviderConfig(config *Config) (challenge.Provider, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("acme-dns: the configuration of the DNS provider is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
serverURL := strings.TrimSpace(config.ServerURL)
|
||||||
|
if serverURL == "" {
|
||||||
|
return nil, errors.New("acme-dns: server_url is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := strings.TrimSpace(config.Credentials)
|
||||||
|
if credentials == "" {
|
||||||
|
return nil, errors.New("acme-dns: credentials is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAccountMappingCredentials(credentials) {
|
||||||
|
return newStorageBackedProvider(serverURL, credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := parseSingleAccountCredentials(credentials)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := goacmedns.NewClient(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account.ServerURL = serverURL
|
||||||
|
|
||||||
|
return &singleAccountProvider{
|
||||||
|
account: account,
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempCredentialsFile(credentials string) (string, error) {
|
||||||
|
tempFile, err := os.CreateTemp("", "allinssl-acmedns-*.json")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("acme-dns: failed to create temp credentials file: %w", err)
|
||||||
|
}
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
if _, err := tempFile.WriteString(credentials); err != nil {
|
||||||
|
return "", fmt.Errorf("acme-dns: failed to write temp credentials file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleAccountProvider) Present(domain, _, keyAuth string) error {
|
||||||
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
|
return p.client.UpdateTXTRecord(context.Background(), p.account, info.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *singleAccountProvider) CleanUp(_, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAccountMappingCredentials(credentials string) bool {
|
||||||
|
var accounts map[string]goacmedns.Account
|
||||||
|
|
||||||
|
return json.Unmarshal([]byte(credentials), &accounts) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSingleAccountCredentials(credentials string) (goacmedns.Account, error) {
|
||||||
|
var account goacmedns.Account
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(credentials), &account); err != nil {
|
||||||
|
return goacmedns.Account{}, fmt.Errorf("acme-dns: credentials must be either a /register account JSON or a map[domain]account JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(account.Username) == "" || strings.TrimSpace(account.Password) == "" || strings.TrimSpace(account.SubDomain) == "" {
|
||||||
|
return goacmedns.Account{}, errors.New("acme-dns: single-account credentials require username, password, and subdomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStorageBackedProvider(serverURL, credentials string) (challenge.Provider, error) {
|
||||||
|
tempFilePath, err := createTempCredentialsFile(credentials)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConfig := legoacmedns.NewDefaultConfig()
|
||||||
|
providerConfig.APIBase = serverURL
|
||||||
|
providerConfig.StoragePath = tempFilePath
|
||||||
|
|
||||||
|
return legoacmedns.NewDNSProviderConfig(providerConfig)
|
||||||
|
}
|
||||||
@@ -201,6 +201,7 @@ func init() {
|
|||||||
|
|
||||||
InsertIfNotExists(db, "access_type", map[string]any{"name": "btdomain", "type": "dns"}, []string{"name", "type"}, []any{"btdomain", "dns"})
|
InsertIfNotExists(db, "access_type", map[string]any{"name": "btdomain", "type": "dns"}, []string{"name", "type"}, []any{"btdomain", "dns"})
|
||||||
InsertIfNotExists(db, "access_type", map[string]any{"name": "edgeone", "type": "dns"}, []string{"name", "type"}, []any{"edgeone", "dns"})
|
InsertIfNotExists(db, "access_type", map[string]any{"name": "edgeone", "type": "dns"}, []string{"name", "type"}, []any{"edgeone", "dns"})
|
||||||
|
InsertIfNotExists(db, "access_type", map[string]any{"name": "acmedns", "type": "dns"}, []string{"name", "type"}, []any{"acmedns", "dns"})
|
||||||
|
|
||||||
err = sqlite_migrate.EnsureDatabaseWithTables(
|
err = sqlite_migrate.EnsureDatabaseWithTables(
|
||||||
"data/site_monitor.db",
|
"data/site_monitor.db",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19933" width="200" height="200"><path d="M749.1 515.5c-23.8 0.1-47.3 4.5-69.6 12.9l-80.1-159.5c28.6-31.1 31.5-78 6.9-112.3-24.6-34.4-69.9-46.8-108.5-29.7s-60.1 58.8-51.3 100.2c8.7 41.3 45.3 70.9 87.5 70.8 3.3 0.2 6.6 0.2 9.8 0l107.6 214.5 27.8-13.9c49.1-28.4 110.6-24.4 155.6 10.2 45 34.6 64.7 92.9 49.9 147.7-14.8 54.8-61.2 95.2-117.5 102.4-56.3 7.2-111.4-20.3-139.5-69.6l-54.3 31.6c49.4 85.6 153.2 123.4 246.1 89.5 92.9-33.9 148-129.6 130.6-227S848 514.9 749.1 515.2v0.3z" fill="#3296FA" p-id="19934"></path><path d="M404.3 463.6L295.7 630.2c-6.8-1.4-13.7-2-20.7-2-41.6-0.1-77.8 28.2-87.7 68.6-9.8 40.4 9.3 82.3 46.3 101.3s82.2 10.2 109.3-21.3 29.1-77.5 4.8-111.2l142.5-218.9-26.1-17c-49.7-28.2-77.4-83.7-70.1-140.3 7.3-56.7 48-103.3 103.2-118.1 55.2-14.8 113.8 5.2 148.5 50.6 34.6 45.4 38.4 107.3 9.5 156.6l54.3 31.2c18.1-30.9 27.6-66 27.5-101.8 1-94.9-63.7-177.9-156.1-200.1-92.3-22.2-187.7 22.4-229.9 107.4s-20.1 188 53.4 248.1v0.3z" fill="#3296FA" p-id="19935"></path><path d="M665.3 749.3c15.1 40.6 57.2 64.6 99.8 57 42.7-7.7 73.7-44.8 73.7-88.2 0-43.4-31.1-80.5-73.7-88.2s-84.7 16.3-99.8 57H415.2v31.2c0 77.6-62.9 140.5-140.5 140.5s-140.5-62.9-140.5-140.5 62.9-140.5 140.5-140.5v-63.1c-108.6-0.6-198.6 84.2-204.4 192.7s74.5 202.4 182.5 213.5c108 11.1 205.8-64.6 222.1-172l190.4 0.6z" fill="#3296FA" p-id="19936"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -297,6 +297,12 @@ export const ApiProjectConfig: Record<string, ApiProjectType> = {
|
|||||||
},
|
},
|
||||||
sort: 38,
|
sort: 38,
|
||||||
},
|
},
|
||||||
|
acmedns: {
|
||||||
|
name: "ACME DNS",
|
||||||
|
icon: "acmedns",
|
||||||
|
type: ["dns"],
|
||||||
|
sort: 39,
|
||||||
|
},
|
||||||
plugin: {
|
plugin: {
|
||||||
name: "插件",
|
name: "插件",
|
||||||
icon: "plugin",
|
icon: "plugin",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export interface AddAccessParams<
|
|||||||
| LecdnAccessConfig
|
| LecdnAccessConfig
|
||||||
| ConstellixAccessConfig
|
| ConstellixAccessConfig
|
||||||
| WebhookAccessConfig
|
| WebhookAccessConfig
|
||||||
|
| AcmeDnsAccessConfig
|
||||||
| SpaceshipAccessConfig
|
| SpaceshipAccessConfig
|
||||||
| BTDomainAccessConfig
|
| BTDomainAccessConfig
|
||||||
> {
|
> {
|
||||||
@@ -102,6 +103,7 @@ export interface UpdateAccessParams<
|
|||||||
| LecdnAccessConfig
|
| LecdnAccessConfig
|
||||||
| ConstellixAccessConfig
|
| ConstellixAccessConfig
|
||||||
| WebhookAccessConfig
|
| WebhookAccessConfig
|
||||||
|
| AcmeDnsAccessConfig
|
||||||
| SpaceshipAccessConfig
|
| SpaceshipAccessConfig
|
||||||
| BTDomainAccessConfig
|
| BTDomainAccessConfig
|
||||||
> extends AddAccessParams<T> {
|
> extends AddAccessParams<T> {
|
||||||
@@ -320,6 +322,11 @@ export interface WebhookAccessConfig {
|
|||||||
ignore_ssl: "0" | "1";
|
ignore_ssl: "0" | "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AcmeDnsAccessConfig {
|
||||||
|
server_url: string;
|
||||||
|
credentials: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SpaceshipAccessConfig {
|
export interface SpaceshipAccessConfig {
|
||||||
api_key: string;
|
api_key: string;
|
||||||
api_secret: string;
|
api_secret: string;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import type {
|
|||||||
LecdnAccessConfig,
|
LecdnAccessConfig,
|
||||||
ConstellixAccessConfig,
|
ConstellixAccessConfig,
|
||||||
WebhookAccessConfig,
|
WebhookAccessConfig,
|
||||||
|
AcmeDnsAccessConfig,
|
||||||
SpaceshipAccessConfig,
|
SpaceshipAccessConfig,
|
||||||
BTDomainAccessConfig,
|
BTDomainAccessConfig,
|
||||||
} from "@/types/access";
|
} from "@/types/access";
|
||||||
@@ -554,6 +555,39 @@ export const useApiFormController = (
|
|||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server_url: {
|
||||||
|
required: true,
|
||||||
|
trigger: "input",
|
||||||
|
validator: (
|
||||||
|
rule: FormItemRule,
|
||||||
|
value: string,
|
||||||
|
callback: (error?: Error) => void
|
||||||
|
) => {
|
||||||
|
if (!isUrl(value)) {
|
||||||
|
return callback(new Error("请输入正确的 Server URL"));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
required: true,
|
||||||
|
trigger: "input",
|
||||||
|
validator: (
|
||||||
|
rule: FormItemRule,
|
||||||
|
value: string,
|
||||||
|
callback: (error?: Error) => void
|
||||||
|
) => {
|
||||||
|
if (!value || value.trim() === "") {
|
||||||
|
return callback(new Error("请输入 Credentials JSON"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return callback(new Error("Credentials 必须是合法 JSON"));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
},
|
||||||
api_key: {
|
api_key: {
|
||||||
trigger: "input",
|
trigger: "input",
|
||||||
validator: (
|
validator: (
|
||||||
@@ -1320,6 +1354,45 @@ export const useApiFormController = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "acmedns":
|
||||||
|
items.push(
|
||||||
|
useFormInput("Server URL", "config.server_url", {
|
||||||
|
allowInput: noSideSpace,
|
||||||
|
placeholder: "https://auth.acme-dns.io",
|
||||||
|
}),
|
||||||
|
useFormTextarea(
|
||||||
|
"Credentials(JSON)",
|
||||||
|
"config.credentials",
|
||||||
|
{
|
||||||
|
rows: 8,
|
||||||
|
placeholder:
|
||||||
|
'{\n "username": "xxx",\n "password": "xxx",\n "subdomain": "xxx",\n "fulldomain": "xxx.auth.acme-dns.io"\n}',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
useFormCustom(() => {
|
||||||
|
return (
|
||||||
|
<NAlert
|
||||||
|
type="info"
|
||||||
|
class="mt-[1.2rem] whitespace-normal"
|
||||||
|
showIcon={false}
|
||||||
|
>
|
||||||
|
<span class="text-[1.3rem]">
|
||||||
|
Credentials 需要填写 acme-dns <code>/register</code>{" "}
|
||||||
|
接口返回的单条 JSON。
|
||||||
|
<a
|
||||||
|
href="https://github.com/acme-dns/acme-dns"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="ml-2 text-[#3296FA] underline"
|
||||||
|
>
|
||||||
|
官方项目地址
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</NAlert>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "spaceship":
|
case "spaceship":
|
||||||
items.push(
|
items.push(
|
||||||
useFormInput("API Key", "config.api_key", {
|
useFormInput("API Key", "config.api_key", {
|
||||||
@@ -1728,6 +1801,12 @@ export const useApiFormController = (
|
|||||||
ignore_ssl: "0",
|
ignore_ssl: "0",
|
||||||
} as WebhookAccessConfig;
|
} as WebhookAccessConfig;
|
||||||
break;
|
break;
|
||||||
|
case "acmedns":
|
||||||
|
param.value.config = {
|
||||||
|
server_url: "https://auth.acme-dns.io",
|
||||||
|
credentials: "",
|
||||||
|
} as AcmeDnsAccessConfig;
|
||||||
|
break;
|
||||||
case "spaceship":
|
case "spaceship":
|
||||||
param.value.config = {
|
param.value.config = {
|
||||||
api_key: "",
|
api_key: "",
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -119,6 +119,7 @@ require (
|
|||||||
github.com/namedotcom/go/v4 v4.0.2 // indirect
|
github.com/namedotcom/go/v4 v4.0.2 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/nrdcg/bunny-go v0.1.0 // indirect
|
github.com/nrdcg/bunny-go v0.1.0 // indirect
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||||
github.com/nrdcg/namesilo v0.5.0 // indirect
|
github.com/nrdcg/namesilo v0.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -598,6 +598,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA=
|
github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA=
|
||||||
github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A=
|
github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A=
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||||
github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE=
|
github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE=
|
||||||
github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw=
|
github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
|||||||
Reference in New Issue
Block a user