mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 04:34:07 +09:00
hscontrol: replace autocert with certmagic
Adds DNS-01 for headscale's own certificate through dns.challenge.provider, with the same propagation check as node certificates. Storage format changes: first start gets a new account and certificate. Updates #2527 Updates #504
This commit is contained in:
1 parent
dcc01dc2b0
commit
5d15cc930d
13 files changed
+639
-134
No files matched your search
+21
-14
@@ -278,7 +278,7 @@ var legacyTLSKeys = map[string]string{
|
||||
var tlsDefaults = map[string]string{
|
||||
"tls.acme.challenge_type": HTTP01ChallengeType,
|
||||
"tls.acme.http_listen": ":http",
|
||||
"tls.acme.cache_dir": "/var/www/.cache",
|
||||
"tls.acme.cache_dir": "/var/lib/headscale/cache",
|
||||
}
|
||||
|
||||
// warnLegacyTLSKeys points at the tls keys for flat keys in the config
|
||||
@@ -749,11 +749,12 @@ func validateServerConfigInto(v *configValidator) {
|
||||
|
||||
validateListenerCollisions(v)
|
||||
|
||||
if ct := tlsKey("tls.acme.challenge_type"); ct != HTTP01ChallengeType && ct != TLSALPN01ChallengeType {
|
||||
if ct := tlsKey("tls.acme.challenge_type"); ct != HTTP01ChallengeType &&
|
||||
ct != TLSALPN01ChallengeType && ct != DNS01ChallengeType {
|
||||
v.Add(&ConfigError{
|
||||
Reason: "tls.acme.challenge_type has an unsupported value",
|
||||
Current: []KV{{"tls.acme.challenge_type", ct}},
|
||||
Allowed: []string{HTTP01ChallengeType, TLSALPN01ChallengeType},
|
||||
Allowed: []string{HTTP01ChallengeType, TLSALPN01ChallengeType, DNS01ChallengeType},
|
||||
Hint: "pick one of the allowed values; HTTP-01 is the default",
|
||||
})
|
||||
}
|
||||
@@ -935,7 +936,21 @@ func validateCertificatesConfig(v *configValidator) {
|
||||
})
|
||||
}
|
||||
|
||||
if !viper.GetBool("dns.certificates.enabled") {
|
||||
enabled := viper.GetBool("dns.certificates.enabled")
|
||||
challengeType := tlsKey("tls.acme.challenge_type")
|
||||
ownDNS01 := tlsKey("tls.acme.hostname") != "" && challengeType == DNS01ChallengeType
|
||||
|
||||
if (enabled || ownDNS01) && viper.GetString("dns.challenge.provider.name") == "" {
|
||||
v.Add(&ConfigError{
|
||||
Reason: "dns.certificates and tls.acme DNS-01 require dns.challenge.provider",
|
||||
Current: []KV{{"dns.certificates.enabled", enabled}, {"tls.acme.challenge_type", challengeType}},
|
||||
Allowed: hsdns.ProviderNames(),
|
||||
Hint: "configure the DNS provider that hosts dns.base_domain or tls.acme.hostname under dns.challenge.provider",
|
||||
See: dnsDocsURL,
|
||||
})
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -948,16 +963,6 @@ func validateCertificatesConfig(v *configValidator) {
|
||||
})
|
||||
}
|
||||
|
||||
if viper.GetString("dns.challenge.provider.name") == "" {
|
||||
v.Add(&ConfigError{
|
||||
Reason: "dns.certificates requires dns.challenge.provider",
|
||||
Current: []KV{{"dns.certificates.enabled", true}},
|
||||
Allowed: hsdns.ProviderNames(),
|
||||
Hint: "configure the DNS provider that hosts dns.base_domain under dns.challenge.provider",
|
||||
See: dnsDocsURL,
|
||||
})
|
||||
}
|
||||
|
||||
base := strings.TrimSuffix(viper.GetString("dns.base_domain"), ".")
|
||||
if base != strings.ToLower(base) || dnsname.ValidHostname(base) != nil {
|
||||
v.Add(&ConfigError{
|
||||
@@ -1017,6 +1022,8 @@ func dnsChallengeConfig() (DNSChallengeConfig, error) {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// Only node certificates let a client pick what is published; a record
|
||||
// shared by headscale's own certificate alone is harmless.
|
||||
if iso, ok := provider.(hsdns.Isolator); ok && viper.GetBool("dns.certificates.enabled") {
|
||||
err = iso.CheckIsolation()
|
||||
if err != nil {
|
||||
|
||||
@@ -1199,6 +1199,47 @@ dns:
|
||||
`,
|
||||
wantErr: "desktop.ts.example.com and laptop.ts.example.com share registration reg",
|
||||
},
|
||||
{
|
||||
name: "acmedns one account for headscale's own certificate",
|
||||
yaml: base + ` challenge:
|
||||
provider:
|
||||
name: acmedns
|
||||
username: user
|
||||
password: pass
|
||||
subdomain: reg
|
||||
server_url: https://auth.acme-dns.io
|
||||
tls:
|
||||
acme:
|
||||
hostname: headscale.example.com
|
||||
challenge_type: DNS-01
|
||||
`,
|
||||
wantZone: "ts.example.com",
|
||||
},
|
||||
{
|
||||
name: "acmedns registration shared with headscale's own name",
|
||||
yaml: base + ` certificates:
|
||||
enabled: true
|
||||
challenge:
|
||||
provider:
|
||||
name: acmedns
|
||||
config:
|
||||
laptop.ts.example.com:
|
||||
username: user
|
||||
password: pass
|
||||
subdomain: reg
|
||||
server_url: https://auth.acme-dns.io
|
||||
headscale.example.com:
|
||||
username: user
|
||||
password: pass
|
||||
subdomain: reg
|
||||
server_url: https://auth.acme-dns.io
|
||||
tls:
|
||||
acme:
|
||||
hostname: headscale.example.com
|
||||
challenge_type: DNS-01
|
||||
`,
|
||||
wantErr: "headscale.example.com and laptop.ts.example.com share registration reg",
|
||||
},
|
||||
{
|
||||
name: "enabled without magic dns",
|
||||
yaml: strings.Replace(base, "magic_dns: true", "magic_dns: false", 1) + ` certificates:
|
||||
@@ -1215,7 +1256,7 @@ dns:
|
||||
yaml: base + ` certificates:
|
||||
enabled: true
|
||||
`,
|
||||
wantErr: "dns.certificates requires dns.challenge.provider",
|
||||
wantErr: "dns.certificates and tls.acme DNS-01 require dns.challenge.provider",
|
||||
},
|
||||
{
|
||||
name: "base_domain outside zone",
|
||||
@@ -1252,6 +1293,15 @@ dns:
|
||||
`,
|
||||
wantErr: "dns.base_domain must be a lowercase DNS name",
|
||||
},
|
||||
{
|
||||
name: "tls DNS-01 without provider",
|
||||
yaml: base + `tls:
|
||||
acme:
|
||||
hostname: headscale.example.com
|
||||
challenge_type: DNS-01
|
||||
`,
|
||||
wantErr: "dns.certificates and tls.acme DNS-01 require dns.challenge.provider",
|
||||
},
|
||||
{
|
||||
name: "negative propagation timeout",
|
||||
yaml: base + ` challenge:
|
||||
@@ -1337,7 +1387,7 @@ dns:
|
||||
want: TLSConfig{ACME: ACMEConfig{
|
||||
ChallengeType: HTTP01ChallengeType,
|
||||
HTTPListen: ":http",
|
||||
CacheDir: "/var/www/.cache",
|
||||
CacheDir: "/var/lib/headscale/cache",
|
||||
}},
|
||||
},
|
||||
{
|
||||
@@ -1372,7 +1422,7 @@ tls:
|
||||
ACME: ACMEConfig{
|
||||
ChallengeType: HTTP01ChallengeType,
|
||||
HTTPListen: ":http",
|
||||
CacheDir: "/var/www/.cache",
|
||||
CacheDir: "/var/lib/headscale/cache",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1404,7 +1454,7 @@ tls_letsencrypt_cache_dir: /var/lib/headscale/.cache
|
||||
ACME: ACMEConfig{
|
||||
ChallengeType: HTTP01ChallengeType,
|
||||
HTTPListen: ":http",
|
||||
CacheDir: "/var/www/.cache",
|
||||
CacheDir: "/var/lib/headscale/cache",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ const (
|
||||
HTTPShutdownTimeout = 3 * time.Second
|
||||
TLSALPN01ChallengeType = "TLS-ALPN-01"
|
||||
HTTP01ChallengeType = "HTTP-01"
|
||||
DNS01ChallengeType = "DNS-01"
|
||||
|
||||
JSONLogFormat = "json"
|
||||
TextLogFormat = "text"
|
||||
|
||||
Reference in new issue
Block a user