diff --git a/acme/challenge.go b/acme/challenge.go index dd97c36ac..c30d00df4 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -63,6 +63,11 @@ var ( // // This variable can be used for testing purposes. InsecurePortTLSALPN01 int + + // StrictFQDN allows to enforce a fully qualified domain name in the DNS + // resolution. By default it allows domain resolution using a search list + // defined in the resolv.conf or similar configuration. + StrictFQDN bool ) // Challenge represents an ACME response Challenge type. @@ -163,8 +168,10 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb // rootedName adds a trailing "." to a given domain name. func rootedName(name string) string { - if name == "" || name[len(name)-1] != '.' { - return name + "." + if StrictFQDN { + if name == "" || name[len(name)-1] != '.' { + return name + "." + } } return name } diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 9f5c16aa1..40f86cb6f 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -2767,19 +2767,34 @@ func Test_serverName(t *testing.T) { func Test_http01ChallengeHost(t *testing.T) { tests := []struct { - name string - value string - want string + name string + strictFQDN bool + value string + want string }{ { - name: "dns", - value: "www.example.com", - want: "www.example.com.", + name: "dns", + strictFQDN: false, + value: "www.example.com", + want: "www.example.com", }, { - name: "rooted dns", - value: "www.example.com.", - want: "www.example.com.", + name: "dns strict", + strictFQDN: true, + value: "www.example.com", + want: "www.example.com.", + }, + { + name: "rooted dns", + strictFQDN: false, + value: "www.example.com.", + want: "www.example.com.", + }, + { + name: "rooted dns strict", + strictFQDN: true, + value: "www.example.com.", + want: "www.example.com.", }, { name: "ipv4", @@ -2794,6 +2809,11 @@ func Test_http01ChallengeHost(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN if got := http01ChallengeHost(tt.value); got != tt.want { t.Errorf("http01ChallengeHost() = %v, want %v", got, tt.want) } @@ -4500,17 +4520,25 @@ func Test_tlsAlpn01ChallengeHost(t *testing.T) { name string } tests := []struct { - name string - args args - want string + name string + strictFQDN bool + args args + want string }{ - {"dns", args{"smallstep.com"}, "smallstep.com."}, - {"rooted dns", args{"smallstep.com."}, "smallstep.com."}, - {"ipv4", args{"1.2.3.4"}, "1.2.3.4"}, - {"ipv6", args{"2607:f8b0:4023:1009::71"}, "2607:f8b0:4023:1009::71"}, + {"dns", false, args{"smallstep.com"}, "smallstep.com"}, + {"dns strict", true, args{"smallstep.com"}, "smallstep.com."}, + {"rooted dns", false, args{"smallstep.com."}, "smallstep.com."}, + {"rooted dns strict", true, args{"smallstep.com."}, "smallstep.com."}, + {"ipv4", true, args{"1.2.3.4"}, "1.2.3.4"}, + {"ipv6", true, args{"2607:f8b0:4023:1009::71"}, "2607:f8b0:4023:1009::71"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN assert.Equal(t, tt.want, tlsAlpn01ChallengeHost(tt.args.name)) }) } @@ -4521,15 +4549,23 @@ func Test_dns01ChallengeHost(t *testing.T) { domain string } tests := []struct { - name string - args args - want string + name string + strictFQDN bool + args args + want string }{ - {"dns", args{"smallstep.com"}, "_acme-challenge.smallstep.com."}, - {"rooted dns", args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, + {"dns", false, args{"smallstep.com"}, "_acme-challenge.smallstep.com"}, + {"dns strict", true, args{"smallstep.com"}, "_acme-challenge.smallstep.com."}, + {"rooted dns", false, args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, + {"rooted dns strict", true, args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN assert.Equal(t, tt.want, dns01ChallengeHost(tt.args.domain)) }) } diff --git a/commands/app.go b/commands/app.go index 1ccaaf3c8..c793f11cd 100644 --- a/commands/app.go +++ b/commands/app.go @@ -83,6 +83,10 @@ Requires **--insecure** flag.`, Usage: `the used on tls-alpn-01 challenges. It can be changed for testing purposes. Requires **--insecure** flag.`, }, + cli.BoolFlag{ + Name: "acme-strict-fqdn", + Usage: `enable strict DNS resolution using a fully qualified domain name.`, + }, cli.StringFlag{ Name: "pidfile", Usage: "the path to the to write the process ID.", @@ -126,6 +130,9 @@ func appAction(ctx *cli.Context) error { } } + // Set the strict DNS resolution on ACME challenges. Defaults to false. + acme.StrictFQDN = ctx.Bool("acme-strict-fqdn") + // Allow custom contexts. if caCtx := ctx.String("context"); caCtx != "" { if _, ok := step.Contexts().Get(caCtx); ok {