Skip to content

Commit 648e6cc

Browse files
Merge pull request #7533 from TheThingsNetwork/fix/acme-host-policy
Support wildcards in ACME hosts
2 parents 2fd4992 + ec38b28 commit 648e6cc

File tree

5 files changed

+76
-7
lines changed

5 files changed

+76
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ For details about compatibility between different releases, see the **Commitment
1313

1414
### Changed
1515

16+
- Support wildcards in the supported hosts for TLS certifictes obtained via ACME (`tls.acme.hosts`).
17+
1618
### Deprecated
1719

1820
### Removed

config/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3626,6 +3626,15 @@
36263626
"file": "listeners.go"
36273627
}
36283628
},
3629+
"error:pkg/config/tlsconfig:acme_host_not_whitelisted": {
3630+
"translations": {
3631+
"en": "host `{host}` for ACME not whitelisted"
3632+
},
3633+
"description": {
3634+
"package": "pkg/config/tlsconfig",
3635+
"file": "config.go"
3636+
}
3637+
},
36293638
"error:pkg/config/tlsconfig:fetch_file": {
36303639
"translations": {
36313640
"en": "fetch file `{name}`"

pkg/config/tlsconfig/config.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import (
2020
"crypto/tls"
2121
"crypto/x509"
2222
"os"
23+
"strings"
2324
"sync/atomic"
2425

2526
"go.thethings.network/lorawan-stack/v3/pkg/errors"
2627
"go.thethings.network/lorawan-stack/v3/pkg/fetch"
2728
"golang.org/x/crypto/acme"
2829
"golang.org/x/crypto/acme/autocert"
30+
"golang.org/x/net/idna"
2931
)
3032

3133
// ACME represents ACME configuration.
@@ -62,7 +64,7 @@ func (a *ACME) Initialize() (*autocert.Manager, error) {
6264
a.manager = &autocert.Manager{
6365
Cache: autocert.DirCache(a.Dir),
6466
Prompt: autocert.AcceptTOS,
65-
HostPolicy: autocert.HostWhitelist(a.Hosts...),
67+
HostPolicy: a.buildHostPolicy(),
6668
Client: &acme.Client{
6769
DirectoryURL: a.Endpoint,
6870
},
@@ -80,6 +82,39 @@ func (a ACME) IsZero() bool {
8082
len(a.Hosts) == 0
8183
}
8284

85+
var errACMEHostNotWhitelisted = errors.DefineFailedPrecondition(
86+
"acme_host_not_whitelisted", "host `{host}` for ACME not whitelisted",
87+
)
88+
89+
func (a ACME) buildHostPolicy() autocert.HostPolicy {
90+
var (
91+
exact = make(map[string]bool, len(a.Hosts))
92+
wildcards = make(map[string]bool, len(a.Hosts))
93+
)
94+
for _, h := range a.Hosts {
95+
h, wildcard := strings.CutPrefix(h, "*.")
96+
h, err := idna.Lookup.ToASCII(h)
97+
if err != nil {
98+
continue
99+
}
100+
if wildcard {
101+
wildcards[h] = true
102+
} else {
103+
exact[h] = true
104+
}
105+
}
106+
return func(_ context.Context, host string) error {
107+
if exact[host] {
108+
return nil
109+
}
110+
parts := strings.SplitN(host, ".", 2)
111+
if len(parts) == 2 && wildcards[parts[1]] {
112+
return nil
113+
}
114+
return errACMEHostNotWhitelisted.WithAttributes("host", host)
115+
}
116+
}
117+
83118
// ServerKeyVault defines configuration for loading a TLS server certificate from the key vault.
84119
type ServerKeyVault struct {
85120
CertificateProvider interface {

pkg/config/tlsconfig/config_test.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import (
2828
"time"
2929

3030
"github.com/smarty/assertions"
31-
. "go.thethings.network/lorawan-stack/v3/pkg/config/tlsconfig"
31+
"go.thethings.network/lorawan-stack/v3/pkg/config/tlsconfig"
32+
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
3233
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
3334
)
3435

@@ -83,8 +84,8 @@ func TestApplyTLSClientConfig(t *testing.T) {
8384
t.Parallel()
8485
a := assertions.New(t)
8586
caCert, _ := genCert()
86-
tlsConfig := &tls.Config{}
87-
err := (&Client{
87+
tlsConfig := &tls.Config{} //nolint:gosec
88+
err := (&tlsconfig.Client{
8889
FileReader: mockFileReader{
8990
"ca.pem": caCert,
9091
},
@@ -96,9 +97,10 @@ func TestApplyTLSClientConfig(t *testing.T) {
9697
a.So(tlsConfig.InsecureSkipVerify, should.BeTrue)
9798

9899
t.Run("Empty", func(t *testing.T) {
100+
t.Parallel()
99101
a := assertions.New(t)
100102
tlsConfig := &tls.Config{} //nolint:gosec
101-
err := (&Client{}).ApplyTo(tlsConfig)
103+
err := (&tlsconfig.Client{}).ApplyTo(tlsConfig)
102104
a.So(err, should.BeNil)
103105
a.So(tlsConfig.RootCAs, should.BeNil)
104106
a.So(tlsConfig.InsecureSkipVerify, should.BeFalse)
@@ -110,7 +112,7 @@ func TestApplyTLSServerAuth(t *testing.T) {
110112
a := assertions.New(t)
111113
cert, key := genCert()
112114
tlsConfig := &tls.Config{} //nolint:gosec
113-
err := (&ServerAuth{
115+
err := (&tlsconfig.ServerAuth{
114116
Source: "file",
115117
FileReader: mockFileReader{
116118
"cert.pem": cert,
@@ -128,7 +130,7 @@ func TestApplyTLSClientAuth(t *testing.T) {
128130
a := assertions.New(t)
129131
cert, key := genCert()
130132
tlsConfig := &tls.Config{} //nolint:gosec
131-
err := (&ClientAuth{
133+
err := (&tlsconfig.ClientAuth{
132134
Source: "file",
133135
FileReader: mockFileReader{
134136
"cert.pem": cert,
@@ -140,3 +142,23 @@ func TestApplyTLSClientAuth(t *testing.T) {
140142
a.So(err, should.BeNil)
141143
a.So(tlsConfig.GetClientCertificate, should.NotBeNil)
142144
}
145+
146+
func TestACMEHosts(t *testing.T) {
147+
t.Parallel()
148+
a, ctx := test.New(t)
149+
acmeConfig := &tlsconfig.ACME{
150+
Enable: true,
151+
Endpoint: "https://acme.example.com/directory",
152+
Dir: "testdata",
153+
154+
Hosts: []string{"example.com", "*.example.org"},
155+
DefaultHost: "example.com",
156+
}
157+
manager, err := acmeConfig.Initialize()
158+
a.So(err, should.BeNil)
159+
a.So(manager.HostPolicy(ctx, "example.com"), should.BeNil)
160+
a.So(manager.HostPolicy(ctx, "subdomain.example.com"), should.NotBeNil)
161+
a.So(manager.HostPolicy(ctx, "example.net"), should.NotBeNil)
162+
a.So(manager.HostPolicy(ctx, "example.org"), should.NotBeNil)
163+
a.So(manager.HostPolicy(ctx, "subdomain.example.org"), should.BeNil)
164+
}

pkg/webui/locales/ja.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,6 +2228,7 @@
22282228
"error:pkg/cluster:peer_unavailable": "クラスター {cluster_role} が利用できません",
22292229
"error:pkg/component:listen_endpoint": "アドレス `{endpoint}` が見当たりません",
22302230
"error:pkg/component:listener": "リスナー `{protocol}` を生成できません",
2231+
"error:pkg/config/tlsconfig:acme_host_not_whitelisted": "",
22312232
"error:pkg/config/tlsconfig:fetch_file": "ファイル`{name}`を取得",
22322233
"error:pkg/config/tlsconfig:missing_acme_default_host": "",
22332234
"error:pkg/config/tlsconfig:missing_acme_dir": "ACEMの保存先が見つかりません",

0 commit comments

Comments
 (0)