diff options
author | 2019-03-13 11:46:30 -0700 | |
---|---|---|
committer | 2019-03-13 18:46:30 +0000 | |
commit | 0d8e1cf8b4ac157ef08837d0ff1e83597b8d1211 (patch) | |
tree | b330ca493dc7f140a9f718c5e7edfbf05b04c9fb | |
parent | f798d18bddddd61ed5469e1ce43dd19522630426 (diff) | |
download | coredns-0d8e1cf8b4ac157ef08837d0ff1e83597b8d1211.tar.gz coredns-0d8e1cf8b4ac157ef08837d0ff1e83597b8d1211.tar.zst coredns-0d8e1cf8b4ac157ef08837d0ff1e83597b8d1211.zip |
[plugin/route53] Support wildcards and other escaped chars. (#2352)
* [plugin/route53] Support wildcards and other escaped chars.
* Fix multiple issues. Add tests.
* Cleanup some comments.
-rw-r--r-- | plugin/route53/route53.go | 74 | ||||
-rw-r--r-- | plugin/route53/route53_test.go | 44 |
2 files changed, 117 insertions, 1 deletions
diff --git a/plugin/route53/route53.go b/plugin/route53/route53.go index 537cf3212..7cc0e9059 100644 --- a/plugin/route53/route53.go +++ b/plugin/route53/route53.go @@ -4,7 +4,10 @@ package route53 import ( "context" + "errors" "fmt" + "strconv" + "strings" "sync" "time" @@ -140,10 +143,79 @@ func (h *Route53) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg return dns.RcodeSuccess, nil } +const escapeSeq = `\\` + +// maybeUnescape parses s and converts escaped ASCII codepoints (in octal) back +// to its ASCII representation. +// +// From AWS docs: +// +// "If the domain name includes any characters other than a to z, 0 to 9, - +// (hyphen), or _ (underscore), Route 53 API actions return the characters as +// escape codes." +// +// For our purposes (and with respect to RFC 1035), we'll fish for a-z, 0-9, +// '-', '.' and '*' as the leftmost character (for wildcards) and throw error +// for everything else. +// +// Example: +// `\\052.example.com.` -> `*.example.com` +// `\\137.example.com.` -> error ('_' is not valid) +func maybeUnescape(s string) (string, error) { + var out string + for { + i := strings.Index(s, escapeSeq) + if i < 0 { + return out + s, nil + } + + out += s[:i] + + li, ri := i+len(escapeSeq), i+len(escapeSeq)+3 + if ri > len(s) { + return "", fmt.Errorf("invalid escape sequence: '%s%s'", escapeSeq, s[li:]) + } + // Parse `\\xxx` in base 8 (2nd arg) and attempt to fit into + // 8-bit result (3rd arg). + n, err := strconv.ParseInt(s[li:ri], 8, 8) + if err != nil { + return "", fmt.Errorf("invalid escape sequence: '%s%s'", escapeSeq, s[li:ri]) + } + + r := rune(n) + switch { + case r >= rune('a') && r <= rune('z'): // Route53 converts everything to lowercase. + case r >= rune('0') && r <= rune('9'): + case r == rune('*'): + if out != "" { + return "", errors.New("`*' ony supported as wildcard (leftmost label)") + } + case r == rune('-'): + case r == rune('.'): + default: + return "", fmt.Errorf("invalid character: %s%#03o", escapeSeq, r) + } + + out += string(r) + + s = s[i+len(escapeSeq)+3:] + } +} + func updateZoneFromRRS(rrs *route53.ResourceRecordSet, z *file.Zone) error { for _, rr := range rrs.ResourceRecords { + + n, err := maybeUnescape(aws.StringValue(rrs.Name)) + if err != nil { + return fmt.Errorf("failed to unescape `%s' name: %v", aws.StringValue(rrs.Name), err) + } + v, err := maybeUnescape(aws.StringValue(rr.Value)) + if err != nil { + return fmt.Errorf("failed to unescape `%s' value: %v", aws.StringValue(rr.Value), err) + } + // Assemble RFC 1035 conforming record to pass into dns scanner. - rfc1035 := fmt.Sprintf("%s %d IN %s %s", aws.StringValue(rrs.Name), aws.Int64Value(rrs.TTL), aws.StringValue(rrs.Type), aws.StringValue(rr.Value)) + rfc1035 := fmt.Sprintf("%s %d IN %s %s", n, aws.Int64Value(rrs.TTL), aws.StringValue(rrs.Type), v) r, err := dns.NewRR(rfc1035) if err != nil { return fmt.Errorf("failed to parse resource record: %v", err) diff --git a/plugin/route53/route53_test.go b/plugin/route53/route53_test.go index 7edec69da..0fc2a9830 100644 --- a/plugin/route53/route53_test.go +++ b/plugin/route53/route53_test.go @@ -36,6 +36,8 @@ func (fakeRoute53) ListResourceRecordSetsPagesWithContext(_ aws.Context, in *rou rType, name, value, hostedZoneID string }{ {"A", "example.org.", "1.2.3.4", "1234567890"}, + {"A", "www.example.org", "1.2.3.4", "1234567890"}, + {"CNAME", `\\052.www.example.org`, "www.example.org", "1234567890"}, {"AAAA", "example.org.", "2001:db8:85a3::8a2e:370:7334", "1234567890"}, {"CNAME", "sample.example.org.", "example.org", "1234567890"}, {"PTR", "example.org.", "ptr.example.org.", "1234567890"}, @@ -203,6 +205,16 @@ func TestRoute53(t *testing.T) { expectedCode: dns.RcodeSuccess, wantAnswer: []string{"other-example.org. 300 IN A 3.5.7.9"}, }, + // 11. *.www.example.org is a wildcard CNAME to www.example.org. + { + qname: "a.www.example.org", + qtype: dns.TypeA, + expectedCode: dns.RcodeSuccess, + wantAnswer: []string{ + "a.www.example.org. 300 IN CNAME www.example.org.", + "www.example.org. 300 IN A 1.2.3.4", + }, + }, } for ti, tc := range tests { @@ -244,3 +256,35 @@ func TestRoute53(t *testing.T) { } } } + +func TestMaybeUnescape(t *testing.T) { + for ti, tc := range []struct { + escaped, want string + wantErr error + }{ + // 0. empty string is fine. + {escaped: "", want: ""}, + // 1. non-escaped sequence. + {escaped: "example.com.", want: "example.com."}, + // 2. escaped `*` as first label - OK. + {escaped: `\\052.example.com`, want: "*.example.com"}, + // 3. Escaped dot, 'a' and a hyphen. No idea why but we'll allow it. + {escaped: `weird\\055ex\\141mple\\056com\\056\\056`, want: "weird-example.com.."}, + // 4. escaped `*` in the middle - NOT OK. + {escaped: `e\\052ample.com`, wantErr: errors.New("`*' ony supported as wildcard (leftmost label)")}, + // 5. Invalid character. + {escaped: `\\000.example.com`, wantErr: errors.New(`invalid character: \\000`)}, + // 6. Invalid escape sequence in the middle. + {escaped: `example\\0com`, wantErr: errors.New(`invalid escape sequence: '\\0co'`)}, + // 7. Invalid escape sequence at the end. + {escaped: `example.com\\0`, wantErr: errors.New(`invalid escape sequence: '\\0'`)}, + } { + got, gotErr := maybeUnescape(tc.escaped) + if tc.wantErr != gotErr && !reflect.DeepEqual(tc.wantErr, gotErr) { + t.Fatalf("Test %d: Expected error: `%v', but got: `%v'", ti, tc.wantErr, gotErr) + } + if tc.want != got { + t.Errorf("Test %d: Expected unescaped: `%s', but got: `%s'", ti, tc.want, got) + } + } +} |