mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
[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.
This commit is contained in:
@@ -4,7 +4,10 @@ package route53
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -140,10 +143,79 @@ func (h *Route53) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
|||||||
return dns.RcodeSuccess, nil
|
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 {
|
func updateZoneFromRRS(rrs *route53.ResourceRecordSet, z *file.Zone) error {
|
||||||
for _, rr := range rrs.ResourceRecords {
|
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.
|
// 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)
|
r, err := dns.NewRR(rfc1035)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse resource record: %v", err)
|
return fmt.Errorf("failed to parse resource record: %v", err)
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ func (fakeRoute53) ListResourceRecordSetsPagesWithContext(_ aws.Context, in *rou
|
|||||||
rType, name, value, hostedZoneID string
|
rType, name, value, hostedZoneID string
|
||||||
}{
|
}{
|
||||||
{"A", "example.org.", "1.2.3.4", "1234567890"},
|
{"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"},
|
{"AAAA", "example.org.", "2001:db8:85a3::8a2e:370:7334", "1234567890"},
|
||||||
{"CNAME", "sample.example.org.", "example.org", "1234567890"},
|
{"CNAME", "sample.example.org.", "example.org", "1234567890"},
|
||||||
{"PTR", "example.org.", "ptr.example.org.", "1234567890"},
|
{"PTR", "example.org.", "ptr.example.org.", "1234567890"},
|
||||||
@@ -203,6 +205,16 @@ func TestRoute53(t *testing.T) {
|
|||||||
expectedCode: dns.RcodeSuccess,
|
expectedCode: dns.RcodeSuccess,
|
||||||
wantAnswer: []string{"other-example.org. 300 IN A 3.5.7.9"},
|
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 {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user