mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 12:29:09 +02:00
* [management] Return the reason when custom domain validation fails The validate endpoint returned an empty 202 and ran the check in a goroutine, so a failed verification only reached the management log. Run the check in the request and return 204 or 412 with a classified reason. Resolver errors are not forwarded. * [management] Address review feedback on custom domain validation Pass the request context to the lookup and store calls now that the check runs in the request, compare the self-resolved name case-insensitively, document the 412 like the other entries, and check the returned error in the existing tests. * [management] Match custom domain CNAME targets case-insensitively DNS names are case-insensitive, so a CNAME pointing at the cluster in different casing should validate instead of failing as a mismatch. * [management] Add validation timeout --------- Co-authored-by: DevBot NetBird <91386968+netbirddev@users.noreply.github.com>
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package domain_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
|
)
|
|
|
|
type resolver struct {
|
|
CNAME string
|
|
}
|
|
|
|
func (r resolver) LookupCNAME(_ context.Context, _ string) (string, error) {
|
|
return r.CNAME, nil
|
|
}
|
|
|
|
func TestIsValid(t *testing.T) {
|
|
tests := map[string]struct {
|
|
resolver interface {
|
|
LookupCNAME(context.Context, string) (string, error)
|
|
}
|
|
domain string
|
|
accept []string
|
|
expect bool
|
|
}{
|
|
"match": {
|
|
resolver: resolver{"bar.example.com."}, // Including trailing "." in response.
|
|
domain: "foo.example.com",
|
|
accept: []string{"bar.example.com"},
|
|
expect: true,
|
|
},
|
|
"no match": {
|
|
resolver: resolver{"invalid"},
|
|
domain: "foo.example.com",
|
|
accept: []string{"bar.example.com"},
|
|
expect: false,
|
|
},
|
|
"match in other case": {
|
|
resolver: resolver{"Bar.Example.COM."},
|
|
domain: "foo.example.com",
|
|
accept: []string{"bar.example.com"},
|
|
expect: true,
|
|
},
|
|
"accept trailing dot": {
|
|
resolver: resolver{"bar.example.com."},
|
|
domain: "foo.example.com",
|
|
accept: []string{"bar.example.com."}, // Including trailing "." in accept.
|
|
expect: true,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
validator := domain.NewValidator(test.resolver)
|
|
actual := validator.IsValid(t.Context(), test.domain, test.accept)
|
|
if test.expect != actual {
|
|
t.Errorf("Incorrect return value:\nexpect: %v\nactual: %v", test.expect, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type errResolver struct {
|
|
err error
|
|
}
|
|
|
|
func (r errResolver) LookupCNAME(context.Context, string) (string, error) {
|
|
return "", r.err
|
|
}
|
|
|
|
func TestValidate_Reason(t *testing.T) {
|
|
const notFound = "no CNAME record found for validation.foo.example.com; point it to eu.proxy.example.com"
|
|
tests := map[string]struct {
|
|
resolver interface {
|
|
LookupCNAME(context.Context, string) (string, error)
|
|
}
|
|
reason domain.ValidationReason
|
|
message string
|
|
}{
|
|
"mismatch": {
|
|
resolver: resolver{"other.example.net."},
|
|
reason: domain.ValidationReasonCNAMEMismatch,
|
|
message: "CNAME record validation.foo.example.com points to other.example.net; point it to eu.proxy.example.com",
|
|
},
|
|
"no cname resolves to itself": {
|
|
resolver: resolver{"validation.foo.example.com."},
|
|
reason: domain.ValidationReasonCNAMENotFound,
|
|
message: notFound,
|
|
},
|
|
"no cname resolves to itself in other case": {
|
|
resolver: resolver{"Validation.Foo.example.com."},
|
|
reason: domain.ValidationReasonCNAMENotFound,
|
|
message: notFound,
|
|
},
|
|
"not found": {
|
|
resolver: errResolver{&net.DNSError{Err: "no such host", Name: "validation.foo.example.com", Server: "10.0.0.2:53", IsNotFound: true}},
|
|
reason: domain.ValidationReasonCNAMENotFound,
|
|
message: notFound,
|
|
},
|
|
"other error is not forwarded": {
|
|
resolver: errResolver{errors.New("dial udp 10.0.0.2:53: connect: network is unreachable")},
|
|
reason: domain.ValidationReasonLookupFailed,
|
|
message: "DNS lookup for validation.foo.example.com failed; retry the validation",
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
_, err := domain.NewValidator(test.resolver).Validate(t.Context(), "foo.example.com", []string{"eu.proxy.example.com"})
|
|
var vErr *domain.ValidationError
|
|
if !errors.As(err, &vErr) {
|
|
t.Fatalf("expected a *domain.ValidationError, got %v", err)
|
|
}
|
|
if vErr.Reason != test.reason || vErr.Message != test.message {
|
|
t.Errorf("expect %q %q, actual %q %q", test.reason, test.message, vErr.Reason, vErr.Message)
|
|
}
|
|
})
|
|
}
|
|
}
|