Skip to content

Commit

Permalink
Do strict DNS lookup on ACME
Browse files Browse the repository at this point in the history
This commit changes the ACME challenges to perform a strict DNS lookup
without taking into account the search list in a resolv.conf
  • Loading branch information
maraino committed Jul 2, 2024
1 parent 8829b42 commit 5c07d20
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 29 deletions.
52 changes: 36 additions & 16 deletions acme/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,40 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
return nil
}

// rootedName adds a trailing "." to a given domain name.
func rootedName(name string) string {
if name == "" || name[len(name)-1] != '.' {
return name + "."
}
return name
}

// http01ChallengeHost checks if a Challenge value is an IPv6 address
// and adds square brackets if that's the case, so that it can be used
// as a hostname. Returns the original Challenge value as the host to
// use in other cases.
func http01ChallengeHost(value string) string {
if ip := net.ParseIP(value); ip != nil && ip.To4() == nil {
value = "[" + value + "]"
if ip := net.ParseIP(value); ip != nil {
if ip.To4() == nil {
value = "[" + value + "]"
}
return value
}
return value
return rootedName(value)
}

// tlsAlpn01ChallengeHost returns the rooted DNS used on TLS-ALPN-01
// validations.
func tlsAlpn01ChallengeHost(name string) string {
if ip := net.ParseIP(name); ip != nil {
return name
}
return rootedName(name)
}

// dns01ChallengeHost returns the TXT record used in DNS-01 validations.
func dns01ChallengeHost(domain string) string {
return "_acme-challenge." + rootedName(domain)
}

func tlsAlert(err error) uint8 {
Expand All @@ -190,13 +215,12 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate
}

var hostPort string

// Allow to change TLS port for testing purposes.
hostPort := tlsAlpn01ChallengeHost(ch.Value)
if port := InsecurePortTLSALPN01; port == 0 {
hostPort = net.JoinHostPort(ch.Value, "443")
hostPort = net.JoinHostPort(hostPort, "443")
} else {
hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port))
hostPort = net.JoinHostPort(hostPort, strconv.Itoa(port))
}

vc := MustClientFromContext(ctx)
Expand All @@ -211,7 +235,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
"cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge"))
}
return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err,
"error doing TLS dial for %s", hostPort))
"error doing TLS dial for %s", ch.Value))
}
defer conn.Close()

Expand Down Expand Up @@ -307,7 +331,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
domain := strings.TrimPrefix(ch.Value, "*.")

vc := MustClientFromContext(ctx)
txtRecords, err := vc.LookupTxt("_acme-challenge." + domain)
txtRecords, err := vc.LookupTxt(dns01ChallengeHost(domain))
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorDNSType, err,
"error looking up TXT records for domain %s", domain))
Expand Down Expand Up @@ -1058,14 +1082,10 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
// should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6.
// It also references TLS Extensions [RFC6066].
func serverName(ch *Challenge) string {
var serverName string
ip := net.ParseIP(ch.Value)
if ip != nil {
serverName = reverseAddr(ip)
} else {
serverName = ch.Value
if ip := net.ParseIP(ch.Value); ip != nil {
return reverseAddr(ip)
}
return serverName
return ch.Value
}

// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
Expand Down
71 changes: 58 additions & 13 deletions acme/challenge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ func TestChallenge_Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -494,7 +494,7 @@ func TestChallenge_Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -536,7 +536,7 @@ func TestChallenge_Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal.:8080/.well-known/acme-challenge/%s: force", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -646,7 +646,7 @@ func TestChallenge_Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value)
err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value)

assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
Expand Down Expand Up @@ -926,7 +926,7 @@ func TestHTTP01Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -961,7 +961,7 @@ func TestHTTP01Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -998,7 +998,7 @@ func TestHTTP01Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s with status code 400", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -1036,7 +1036,7 @@ func TestHTTP01Validate(t *testing.T) {
assert.Equal(t, "zap.internal", updch.Value)
assert.Equal(t, StatusPending, updch.Status)

err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.Token)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal./.well-known/acme-challenge/%s with status code 400", ch.Token)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
Expand Down Expand Up @@ -1065,7 +1065,7 @@ func TestHTTP01Validate(t *testing.T) {
}, nil
},
},
err: NewErrorISE("error reading response body for url http://zap.internal/.well-known/acme-challenge/%s: force", ch.Token),
err: NewErrorISE("error reading response body for url http://zap.internal./.well-known/acme-challenge/%s: force", ch.Token),
}
},
"fail/key-auth-gen-error": func(t *testing.T) test {
Expand Down Expand Up @@ -1723,7 +1723,7 @@ func TestTLSALPN01Validate(t *testing.T) {
assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type)
assert.Equal(t, "zap.internal", updch.Value)

err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value)
err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value)

assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
Expand Down Expand Up @@ -1754,7 +1754,7 @@ func TestTLSALPN01Validate(t *testing.T) {
assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type)
assert.Equal(t, "zap.internal", updch.Value)

err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value)
err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value)

assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
Expand Down Expand Up @@ -1786,7 +1786,7 @@ func TestTLSALPN01Validate(t *testing.T) {
assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type)
assert.Equal(t, "zap.internal", updch.Value)

err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: context deadline exceeded", ch.Value)
err := NewError(ErrorConnectionType, "error doing TLS dial for %v: context deadline exceeded", ch.Value)

assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
Expand Down Expand Up @@ -2774,7 +2774,12 @@ func Test_http01ChallengeHost(t *testing.T) {
{
name: "dns",
value: "www.example.com",
want: "www.example.com",
want: "www.example.com.",
},
{
name: "rooted dns",
value: "www.example.com.",
want: "www.example.com.",
},
{
name: "ipv4",
Expand Down Expand Up @@ -4301,3 +4306,43 @@ func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString
Value: rawBytes,
}, nil
}

func Test_tlsAlpn01ChallengeHost(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
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"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tlsAlpn01ChallengeHost(tt.args.name))
})
}
}

func Test_dns01ChallengeHost(t *testing.T) {
type args struct {
domain string
}
tests := []struct {
name string
args args
want string
}{
{"dns", args{"smallstep.com"}, "_acme-challenge.smallstep.com."},
{"rooted dns", args{"smallstep.com."}, "_acme-challenge.smallstep.com."},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, dns01ChallengeHost(tt.args.domain))
})
}
}

0 comments on commit 5c07d20

Please sign in to comment.