[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:
dilyevsky
2019-03-13 11:46:30 -07:00
committed by Miek Gieben
parent f798d18bdd
commit 0d8e1cf8b4
2 changed files with 117 additions and 1 deletions

View File

@@ -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)