diff --git a/.changelog/12535.txt b/.changelog/12535.txt new file mode 100644 index 0000000000..8376d596bc --- /dev/null +++ b/.changelog/12535.txt @@ -0,0 +1,3 @@ +```release-note:bug +dns: allow max of 63 character DNS labels instead of 64 per RFC 1123 +``` diff --git a/agent/dns/validation.go b/agent/dns/validation.go new file mode 100644 index 0000000000..8a73b0dd09 --- /dev/null +++ b/agent/dns/validation.go @@ -0,0 +1,27 @@ +package dns + +import ( + "errors" + "regexp" +) + +// matches valid DNS labels according to RFC 1123, should be at most 63 +// characters according to the RFC +var validLabel = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?$`) + +// IsValidLabel returns true if the string given is a valid DNS label (RFC 1123). +// Note: the only difference between RFC 1035 and RFC 1123 labels is that in +// RFC 1123 labels can begin with a number. +func IsValidLabel(name string) bool { + return validLabel.MatchString(name) +} + +// ValidateLabel is similar to IsValidLabel except it returns an error +// instead of false when name is not a valid DNS label. The error will contain +// reference to what constitutes a valid DNS label. +func ValidateLabel(name string) error { + if !IsValidLabel(name) { + return errors.New("a valid DNS label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character") + } + return nil +} diff --git a/agent/dns/validation_test.go b/agent/dns/validation_test.go new file mode 100644 index 0000000000..b4b44a7f89 --- /dev/null +++ b/agent/dns/validation_test.go @@ -0,0 +1,50 @@ +package dns_test + +import ( + "testing" + + "github.com/hashicorp/consul/agent/dns" + "github.com/stretchr/testify/require" +) + +func TestValidLabel(t *testing.T) { + cases := map[string]bool{ + "CrEaTeD": true, + "created": true, + "create-deleted": true, + "foo": true, + "": false, + "_foo_": false, + "-foo": false, + "foo-": false, + "-foo-": false, + "-foo-bar-": false, + "no spaces allowed": false, + "thisvaluecontainsalotofcharactersbutnottoomanyandthecaseisatrue": true, // 63 chars + "thisvaluecontainstoomanycharactersandisthusinvalidandtestisfalse": false, // 64 chars + } + + t.Run("*", func(t *testing.T) { + t.Run("IsValidLabel", func(t *testing.T) { + require.False(t, dns.IsValidLabel("*")) + }) + t.Run("ValidateLabel", func(t *testing.T) { + require.Error(t, dns.ValidateLabel("*")) + }) + }) + + for name, expect := range cases { + t.Run(name, func(t *testing.T) { + t.Run("IsValidDNSLabel", func(t *testing.T) { + require.Equal(t, expect, dns.IsValidLabel(name)) + }) + t.Run("ValidateLabel", func(t *testing.T) { + if expect { + require.NoError(t, dns.ValidateLabel(name)) + } else { + require.Error(t, dns.ValidateLabel(name)) + } + }) + }) + } +}