Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions credentials/xds/xds_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func newTestContextWithHandshakeInfo(parent context.Context, root, identity cert
// NewSubConn().
var sms []matcher.StringMatcher
if sanExactMatch != "" {
sms = []matcher.StringMatcher{matcher.StringMatcherForTesting(newStringP(sanExactMatch), nil, nil, nil, nil, false)}
sms = []matcher.StringMatcher{matcher.NewExactStringMatcher(sanExactMatch, false)}
}
info := xdsinternal.NewHandshakeInfo(root, identity, sms, false)
uPtr := unsafe.Pointer(info)
Expand Down Expand Up @@ -542,7 +542,7 @@ func (s) TestClientCredsProviderSwitch(t *testing.T) {
// Create a root provider which will fail the handshake because it does not
// use the correct trust roots.
root1 := makeRootProvider(t, "x509/client_ca_cert.pem")
handshakeInfo := xdsinternal.NewHandshakeInfo(root1, nil, []matcher.StringMatcher{matcher.StringMatcherForTesting(newStringP(defaultTestCertSAN), nil, nil, nil, nil, false)}, false)
handshakeInfo := xdsinternal.NewHandshakeInfo(root1, nil, []matcher.StringMatcher{matcher.NewExactStringMatcher(defaultTestCertSAN, false)}, false)
// We need to repeat most of what newTestContextWithHandshakeInfo() does
// here because we need access to the underlying HandshakeInfo so that we
// can update it before the next call to ClientHandshake().
Expand All @@ -568,7 +568,7 @@ func (s) TestClientCredsProviderSwitch(t *testing.T) {
// Create a new root provider which uses the correct trust roots. And update
// the HandshakeInfo with the new provider.
root2 := makeRootProvider(t, "x509/server_ca_cert.pem")
handshakeInfo = xdsinternal.NewHandshakeInfo(root2, nil, []matcher.StringMatcher{matcher.StringMatcherForTesting(newStringP(defaultTestCertSAN), nil, nil, nil, nil, false)}, false)
handshakeInfo = xdsinternal.NewHandshakeInfo(root2, nil, []matcher.StringMatcher{matcher.NewExactStringMatcher(defaultTestCertSAN, false)}, false)
// Update the existing pointer, which address attribute will continue to
// point to.
atomic.StorePointer(&uPtr, unsafe.Pointer(handshakeInfo))
Expand Down Expand Up @@ -596,7 +596,3 @@ func (s) TestClientClone(t *testing.T) {
t.Fatal("return value from Clone() doesn't point to new credentials instance")
}
}

func newStringP(s string) *string {
return &s
}
84 changes: 40 additions & 44 deletions internal/credentials/xds/handshake_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,40 +170,40 @@ func TestMatchingSANExists_FailureCases(t *testing.T) {
{
desc: "exact match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("abcd.test.com"), nil, nil, nil, nil, false),
matcher.StringMatcherForTesting(newStringP("http://golang"), nil, nil, nil, nil, false),
matcher.StringMatcherForTesting(newStringP("HTTP://GOLANG.ORG"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("abcd.test.com", false),
matcher.NewExactStringMatcher("http://golang", false),
matcher.NewExactStringMatcher("HTTP://GOLANG.ORG", false),
},
},
{
desc: "prefix match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, newStringP("i-aint-the-one"), nil, nil, nil, false),
matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
matcher.StringMatcherForTesting(nil, newStringP("FOO.BAR"), nil, nil, nil, false),
matcher.NewPrefixStringMatcher("i-aint-the-one", false),
matcher.NewPrefixStringMatcher("192.168.1.1", false),
matcher.NewPrefixStringMatcher("FOO.BAR", false),
},
},
{
desc: "suffix match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, newStringP("i-aint-the-one"), nil, nil, false),
matcher.StringMatcherForTesting(nil, nil, newStringP("1::68"), nil, nil, false),
matcher.StringMatcherForTesting(nil, nil, newStringP(".COM"), nil, nil, false),
matcher.NewSuffixStringMatcher("i-aint-the-one", false),
matcher.NewSuffixStringMatcher("1::68", false),
matcher.NewSuffixStringMatcher(".COM", false),
},
},
{
desc: "regex match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.examples\.com`), false),
matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
matcher.NewRegexStringMatcher(regexp.MustCompile(`.*\.examples\.com`)),
matcher.NewRegexStringMatcher(regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)),
},
},
{
desc: "contains match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("i-aint-the-one"), nil, false),
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:db8:1:1::68"), nil, false),
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, false),
matcher.NewContainsStringMatcher("i-aint-the-one", false),
matcher.NewContainsStringMatcher("2001:db8:1:1::68", false),
matcher.NewContainsStringMatcher("GRPC", false),
},
},
}
Expand Down Expand Up @@ -248,65 +248,65 @@ func TestMatchingSANExists_Success(t *testing.T) {
{
desc: "exact match dns wildcard",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
matcher.StringMatcherForTesting(newStringP("abc.example.com"), nil, nil, nil, nil, false),
matcher.NewPrefixStringMatcher("192.168.1.1", false),
matcher.NewExactStringMatcher("https://github.com/grpc/grpc-java", false),
matcher.NewExactStringMatcher("abc.example.com", false),
},
},
{
desc: "exact match ignore case",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("FOOBAR@EXAMPLE.COM"), nil, nil, nil, nil, true),
matcher.NewExactStringMatcher("FOOBAR@EXAMPLE.COM", true),
},
},
{
desc: "prefix match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, newStringP(".co.in"), nil, nil, false),
matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
matcher.StringMatcherForTesting(nil, newStringP("baz.test"), nil, nil, nil, false),
matcher.NewSuffixStringMatcher(".co.in", false),
matcher.NewPrefixStringMatcher("192.168.1.1", false),
matcher.NewPrefixStringMatcher("baz.test", false),
},
},
{
desc: "prefix match ignore case",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, newStringP("BAZ.test"), nil, nil, nil, true),
matcher.NewPrefixStringMatcher("BAZ.test", true),
},
},
{
desc: "suffix match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
matcher.StringMatcherForTesting(nil, nil, newStringP("192.168.1.1"), nil, nil, false),
matcher.StringMatcherForTesting(nil, nil, newStringP("@test.com"), nil, nil, false),
matcher.NewRegexStringMatcher(regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)),
matcher.NewSuffixStringMatcher("192.168.1.1", false),
matcher.NewSuffixStringMatcher("@test.com", false),
},
},
{
desc: "suffix match ignore case",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, newStringP("@test.COM"), nil, nil, true),
matcher.NewSuffixStringMatcher("@test.COM", true),
},
},
{
desc: "regex match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("https://github.com/grpc/grpc-java"), nil, false),
matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.test\.com`), false),
matcher.NewContainsStringMatcher("https://github.com/grpc/grpc-java", false),
matcher.NewRegexStringMatcher(regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)),
matcher.NewRegexStringMatcher(regexp.MustCompile(`.*\.test\.com`)),
},
},
{
desc: "contains match",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:68::db8"), nil, false),
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("192.0.0"), nil, false),
matcher.NewExactStringMatcher("https://github.com/grpc/grpc-java", false),
matcher.NewContainsStringMatcher("2001:68::db8", false),
matcher.NewContainsStringMatcher("192.0.0", false),
},
},
{
desc: "contains match ignore case",
sanMatchers: []matcher.StringMatcher{
matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, true),
matcher.NewContainsStringMatcher("GRPC", true),
},
},
}
Expand All @@ -322,10 +322,6 @@ func TestMatchingSANExists_Success(t *testing.T) {
}
}

func newStringP(s string) *string {
return &s
}

func TestEqual(t *testing.T) {
tests := []struct {
desc string
Expand Down Expand Up @@ -354,31 +350,31 @@ func TestEqual(t *testing.T) {
{
desc: "same providers, same SAN matchers",
hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("foo.com", false),
}, false),
hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("foo.com", false),
}, false),
wantMatch: true,
},
{
desc: "same providers, different SAN matchers",
hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("foo.com", false),
}, false),
hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("bar.com", false),
}, false),
wantMatch: false,
},
{
desc: "same SAN matchers with different content",
hi1: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("foo.com", false),
}, false),
hi2: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
matcher.NewExactStringMatcher("foo.com", false),
matcher.NewExactStringMatcher("bar.com", false),
}, false),
wantMatch: false,
},
Expand Down
119 changes: 77 additions & 42 deletions internal/xds/matcher/string_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,49 @@ type StringMatcher struct {

// Match returns true if input matches the criteria in the given StringMatcher.
func (sm StringMatcher) Match(input string) bool {
if sm.ignoreCase {
input = strings.ToLower(input)
}
switch {
case sm.exactMatch != nil:
if sm.ignoreCase {
input = strings.ToLower(input)
}
return input == *sm.exactMatch
case sm.prefixMatch != nil:
if sm.ignoreCase {
input = strings.ToLower(input)
}
return strings.HasPrefix(input, *sm.prefixMatch)
case sm.suffixMatch != nil:
if sm.ignoreCase {
input = strings.ToLower(input)
}
return strings.HasSuffix(input, *sm.suffixMatch)
case sm.regexMatch != nil:
return grpcutil.FullMatchWithRegex(sm.regexMatch, input)
case sm.containsMatch != nil:
if sm.ignoreCase {
input = strings.ToLower(input)
}
return strings.Contains(input, *sm.containsMatch)
case sm.regexMatch != nil:
return grpcutil.FullMatchWithRegex(sm.regexMatch, input)
}
return false
}

// newStrPtr allocates a new string that holds the value of input and returns a
// pointer to it. ignoreCase controls if a lower case version of input is used.
func newStrPtr(input *string, ignoreCase bool) *string {
if input == nil {
return nil
}

s := new(string)
if ignoreCase {
*s = strings.ToLower(*input)
} else {
*s = *input
}
return s
}

// StringMatcherFromProto is a helper function to create a StringMatcher from
// the corresponding StringMatcher proto.
//
Expand All @@ -78,26 +103,17 @@ func StringMatcherFromProto(matcherProto *v3matcherpb.StringMatcher) (StringMatc
matcher := StringMatcher{ignoreCase: matcherProto.GetIgnoreCase()}
switch mt := matcherProto.GetMatchPattern().(type) {
case *v3matcherpb.StringMatcher_Exact:
matcher.exactMatch = &mt.Exact
if matcher.ignoreCase {
*matcher.exactMatch = strings.ToLower(*matcher.exactMatch)
}
matcher.exactMatch = newStrPtr(&mt.Exact, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_Prefix:
if matcherProto.GetPrefix() == "" {
return StringMatcher{}, errors.New("empty prefix is not allowed in StringMatcher")
}
matcher.prefixMatch = &mt.Prefix
if matcher.ignoreCase {
*matcher.prefixMatch = strings.ToLower(*matcher.prefixMatch)
}
matcher.prefixMatch = newStrPtr(&mt.Prefix, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_Suffix:
if matcherProto.GetSuffix() == "" {
return StringMatcher{}, errors.New("empty suffix is not allowed in StringMatcher")
}
matcher.suffixMatch = &mt.Suffix
if matcher.ignoreCase {
*matcher.suffixMatch = strings.ToLower(*matcher.suffixMatch)
}
matcher.suffixMatch = newStrPtr(&mt.Suffix, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_SafeRegex:
regex := matcherProto.GetSafeRegex().GetRegex()
re, err := regexp.Compile(regex)
Expand All @@ -109,40 +125,59 @@ func StringMatcherFromProto(matcherProto *v3matcherpb.StringMatcher) (StringMatc
if matcherProto.GetContains() == "" {
return StringMatcher{}, errors.New("empty contains is not allowed in StringMatcher")
}
matcher.containsMatch = &mt.Contains
if matcher.ignoreCase {
*matcher.containsMatch = strings.ToLower(*matcher.containsMatch)
}
matcher.containsMatch = newStrPtr(&mt.Contains, matcher.ignoreCase)
default:
return StringMatcher{}, fmt.Errorf("unrecognized string matcher: %+v", matcherProto)
}
return matcher, nil
}

// StringMatcherForTesting is a helper function to create a StringMatcher based
// on the given arguments. Intended only for testing purposes.
func StringMatcherForTesting(exact, prefix, suffix, contains *string, regex *regexp.Regexp, ignoreCase bool) StringMatcher {
sm := StringMatcher{
exactMatch: exact,
prefixMatch: prefix,
suffixMatch: suffix,
regexMatch: regex,
containsMatch: contains,
// NewExactStringMatcher creates a string matcher that requires the input string
// to exactly match the pattern specified here. The match will be case
// insensitive if ignore_case is true.
func NewExactStringMatcher(pattern string, ignoreCase bool) StringMatcher {
return StringMatcher{
exactMatch: newStrPtr(&pattern, ignoreCase),
ignoreCase: ignoreCase,
}
}

// NewPrefixStringMatcher creates a string matcher that requires the input
// string to contain the prefix specified here. The match will be case
// insensitive if ignore_case is true.
func NewPrefixStringMatcher(prefix string, ignoreCase bool) StringMatcher {
return StringMatcher{
prefixMatch: newStrPtr(&prefix, ignoreCase),
ignoreCase: ignoreCase,
}
}

// NewSuffixStringMatcher creates a string matcher that requires the input
// string to contain the suffix specified here. The match will be case
// insensitive if ignore_case is true.
func NewSuffixStringMatcher(suffix string, ignoreCase bool) StringMatcher {
return StringMatcher{
suffixMatch: newStrPtr(&suffix, ignoreCase),
ignoreCase: ignoreCase,
}
}

// NewContainsStringMatcher creates a string matcher that requires the input
// string to contain the pattern specified here. The match will be case
// insensitive if ignore_case is true.
func NewContainsStringMatcher(pattern string, ignoreCase bool) StringMatcher {
return StringMatcher{
containsMatch: newStrPtr(&pattern, ignoreCase),
ignoreCase: ignoreCase,
}
if ignoreCase {
switch {
case sm.exactMatch != nil:
*sm.exactMatch = strings.ToLower(*exact)
case sm.prefixMatch != nil:
*sm.prefixMatch = strings.ToLower(*prefix)
case sm.suffixMatch != nil:
*sm.suffixMatch = strings.ToLower(*suffix)
case sm.containsMatch != nil:
*sm.containsMatch = strings.ToLower(*contains)
}
}

// NewRegexStringMatcher creates a string matcher that requires the input string
// to match the regular expression specified here.
func NewRegexStringMatcher(regex *regexp.Regexp) StringMatcher {
return StringMatcher{
regexMatch: regex,
}
return sm
}

// ExactMatch returns the value of the configured exact match or an empty string
Expand Down
Loading