Skip to content

Commit 986152f

Browse files
feat: add domain name validation (#925)
1 parent 7d4af47 commit 986152f

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

instance/conn_name.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var (
2727
// Additionally, we have to support legacy "domain-scoped" projects
2828
// (e.g. "google.com:PROJECT")
2929
connNameRegex = regexp.MustCompile("([^:]+(:[^:]+)?):([^:]+):([^:]+)")
30+
// The domain name pattern in accordance with RFC 1035, RFC 1123 and RFC 2181.
31+
domainNameRegex = regexp.MustCompile(`^(?:[_a-z0-9](?:[_a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?)?$`)
3032
)
3133

3234
// ConnName represents the "instance connection name", in the format
@@ -65,11 +67,21 @@ func (c *ConnName) DomainName() string {
6567
return c.domainName
6668
}
6769

68-
// HasDomainName returns the Cloud SQL domain name
70+
// HasDomainName returns whether the Cloud SQL instance has a domain name
6971
func (c *ConnName) HasDomainName() bool {
7072
return c.domainName != ""
7173
}
7274

75+
// IsValidDomain validates that a string is a well-formed domain name
76+
func IsValidDomain(dn string) bool {
77+
b := []byte(dn)
78+
m := domainNameRegex.FindSubmatch(b)
79+
if m == nil {
80+
return false
81+
}
82+
return true
83+
}
84+
7385
// ParseConnName initializes a new ConnName struct.
7486
func ParseConnName(cn string) (ConnName, error) {
7587
return ParseConnNameWithDomainName(cn, "")

instance/conn_name_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,42 @@ func TestParseConnName(t *testing.T) {
4545
}
4646
}
4747
}
48+
49+
func TestIsValidDomain(t *testing.T) {
50+
tests := []struct {
51+
domain string
52+
want bool
53+
}{
54+
{
55+
domain: "prod-db.mycompany.example.com",
56+
want: true,
57+
},
58+
{
59+
domain: "example.com.", // trailing dot
60+
want: true,
61+
},
62+
{
63+
domain: "-example.com", // leading hyphen
64+
want: false,
65+
},
66+
{
67+
domain: "example", // missing TLD
68+
want: false,
69+
},
70+
{
71+
domain: "127.0.0.1", // IPv4 address
72+
want: false,
73+
},
74+
{
75+
domain: "0:0:0:0:0:0:0:1", // IPv6 address
76+
want: false,
77+
},
78+
}
79+
80+
for _, tc := range tests {
81+
v := IsValidDomain(tc.domain)
82+
if v != tc.want {
83+
t.Errorf("IsValidDomainName(%s) failed: want %v, got %v", tc.domain, tc.want, v)
84+
}
85+
}
86+
}

internal/cloudsql/resolver.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"net"
2121
"sort"
2222

23+
"cloud.google.com/go/cloudsqlconn/errtype"
2324
"cloud.google.com/go/cloudsqlconn/instance"
2425
)
2526

@@ -63,10 +64,21 @@ type DNSInstanceConnectionNameResolver struct {
6364
func (r *DNSInstanceConnectionNameResolver) Resolve(ctx context.Context, icn string) (instanceName instance.ConnName, err error) {
6465
cn, err := instance.ParseConnName(icn)
6566
if err != nil {
66-
// The connection name was not project:region:instance
67-
// Attempt to query a TXT record and see if it works instead.
68-
cn, err = r.queryDNS(ctx, icn)
69-
if err != nil {
67+
// The connection name was not in project:region:instance format.
68+
// Check that connection name is a valid DNS domain name.
69+
if instance.IsValidDomain(icn) {
70+
// Attempt to query a TXT record and see if it works instead.
71+
cn, err = r.queryDNS(ctx, icn)
72+
if err != nil {
73+
return instance.ConnName{}, err
74+
}
75+
} else {
76+
// Connection name is not valid instance connection name or domain name
77+
err := errtype.NewConfigError(
78+
"invalid connection name, expected PROJECT:REGION:INSTANCE "+
79+
"format or valid DNS domain name",
80+
icn,
81+
)
7082
return instance.ConnName{}, err
7183
}
7284
}

0 commit comments

Comments
 (0)