aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar dilyevsky <ilyevsky@gmail.com> 2019-03-13 11:46:30 -0700
committerGravatar Miek Gieben <miek@miek.nl> 2019-03-13 18:46:30 +0000
commit0d8e1cf8b4ac157ef08837d0ff1e83597b8d1211 (patch)
treeb330ca493dc7f140a9f718c5e7edfbf05b04c9fb
parentf798d18bddddd61ed5469e1ce43dd19522630426 (diff)
downloadcoredns-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.go74
-rw-r--r--plugin/route53/route53_test.go44
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)
+ }
+ }
+}