Skip to content

Commit

Permalink
support ignore_case
Browse files Browse the repository at this point in the history
  • Loading branch information
easwars committed Mar 5, 2021
1 parent f48d45b commit 2640699
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 49 deletions.
4 changes: 2 additions & 2 deletions credentials/xds/xds_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func newTestContextWithHandshakeInfo(parent context.Context, root, identity cert
// NewSubConn().
info := xdsinternal.NewHandshakeInfo(root, identity)
if sanExactMatch != "" {
info.SetSANMatchers([]xdsinternal.SubjectAltNameMatcher{{ExactMatch: newStringP(sanExactMatch)}})
info.SetSANMatchers([]xdsinternal.StringMatcher{{ExactMatch: newStringP(sanExactMatch)}})
}
addr := xdsinternal.SetHandshakeInfo(resolver.Address{}, info)

Expand Down Expand Up @@ -534,7 +534,7 @@ func (s) TestClientCredsProviderSwitch(t *testing.T) {
// use the correct trust roots.
root1 := makeRootProvider(t, "x509/client_ca_cert.pem")
handshakeInfo := xdsinternal.NewHandshakeInfo(root1, nil)
handshakeInfo.SetSANMatchers([]xdsinternal.SubjectAltNameMatcher{{ExactMatch: newStringP(defaultTestCertSAN)}})
handshakeInfo.SetSANMatchers([]xdsinternal.StringMatcher{{ExactMatch: newStringP(defaultTestCertSAN)}})

// We need to repeat most of what newTestContextWithHandshakeInfo() does
// here because we need access to the underlying HandshakeInfo so that we
Expand Down
76 changes: 49 additions & 27 deletions internal/credentials/xds/handshake_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,18 @@ func init() {
internal.GetXDSHandshakeInfoForTesting = GetHandshakeInfo
}

// SubjectAltNameMatcher contains a match criteria for SANs. Since these fields
// are part of a `oneof` in the corresponding xDS proto, only one of the
// following fields is expected to be set.
type SubjectAltNameMatcher struct {
// StringMatcher contains match criteria for matching a string.
type StringMatcher struct {
// Since these match fields are part of a `oneof` in the corresponding xDS
// proto, only one of them is expected to be set.
ExactMatch *string
RegexMatch *regexp.Regexp
PrefixMatch *string
SuffixMatch *string
RegexMatch *regexp.Regexp
ContainsMatch *string
// If true, indicates the exact/prefix/suffix/contains matching should be
// case insensitive. This has no effect on the regex match.
IgnoreCase bool
}

// handshakeAttrKey is the type used as the key to store HandshakeInfo in
Expand Down Expand Up @@ -77,8 +80,8 @@ type HandshakeInfo struct {
mu sync.Mutex
rootProvider certprovider.Provider
identityProvider certprovider.Provider
sanMatchers []SubjectAltNameMatcher // Only on the client side.
requireClientCert bool // Only on server side.
sanMatchers []StringMatcher // Only on the client side.
requireClientCert bool // Only on server side.
}

// SetRootCertProvider updates the root certificate provider.
Expand All @@ -96,7 +99,7 @@ func (hi *HandshakeInfo) SetIdentityCertProvider(identity certprovider.Provider)
}

// SetSANMatchers updates the list of SAN matchers.
func (hi *HandshakeInfo) SetSANMatchers(sanMatchers []SubjectAltNameMatcher) {
func (hi *HandshakeInfo) SetSANMatchers(sanMatchers []StringMatcher) {
hi.mu.Lock()
hi.sanMatchers = sanMatchers
hi.mu.Unlock()
Expand Down Expand Up @@ -233,19 +236,10 @@ func (hi *HandshakeInfo) MatchingSANExists(cert *x509.Certificate) bool {
// Caller must hold mu.
func (hi *HandshakeInfo) matchSAN(san string, isDNS bool) bool {
for _, matcher := range hi.sanMatchers {
if matcher.IgnoreCase {
san = strings.ToLower(san)
}
switch {
case matcher.ContainsMatch != nil:
if strings.Contains(san, *matcher.ContainsMatch) {
return true
}
case matcher.SuffixMatch != nil:
if strings.HasSuffix(san, *matcher.SuffixMatch) {
return true
}
case matcher.PrefixMatch != nil:
if strings.HasPrefix(san, *matcher.PrefixMatch) {
return true
}
case matcher.ExactMatch != nil:
if isDNS {
// This is a special case which is documented in the xDS protos.
Expand All @@ -255,15 +249,44 @@ func (hi *HandshakeInfo) matchSAN(san string, isDNS bool) bool {
if dnsMatch(*matcher.ExactMatch, san) {
return true
}
} else {
if san == *matcher.ExactMatch {
return true
}
continue
}

pattern := *matcher.ExactMatch
if matcher.IgnoreCase {
pattern = strings.ToLower(pattern)
}
if san == pattern {
return true
}
case matcher.PrefixMatch != nil:
pattern := *matcher.PrefixMatch
if matcher.IgnoreCase {
pattern = strings.ToLower(pattern)
}
if strings.HasPrefix(san, pattern) {
return true
}
case matcher.SuffixMatch != nil:
pattern := *matcher.SuffixMatch
if matcher.IgnoreCase {
pattern = strings.ToLower(pattern)
}
if strings.HasSuffix(san, pattern) {
return true
}
case matcher.RegexMatch != nil:
if matcher.RegexMatch.MatchString(san) {
return true
}
case matcher.ContainsMatch != nil:
pattern := *matcher.ContainsMatch
if matcher.IgnoreCase {
pattern = strings.ToLower(pattern)
}
if strings.Contains(san, pattern) {
return true
}
}
}
return false
Expand All @@ -272,15 +295,14 @@ func (hi *HandshakeInfo) matchSAN(san string, isDNS bool) bool {
// dnsMatch implements a DNS wildcard matching algorithm based on RFC2828 and
// grpc-java's implementation in `OkHostnameVerifier` class.
func dnsMatch(host, pattern string) bool {
// Normalize the host name and the pattern by turning them into absolute
// domain names and make them lower-case as domain names are
// case-insensitive.
// Add trailing "." and turn them into absolute domain names.
if !strings.HasSuffix(host, ".") {
host += "."
}
if !strings.HasSuffix(pattern, ".") {
pattern += "."
}
// Domain names are case-insensitive.
host = strings.ToLower(host)
pattern = strings.ToLower(pattern)

Expand Down
24 changes: 12 additions & 12 deletions internal/credentials/xds/handshake_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,39 +142,39 @@ func TestMatchingSANExists_FailureCases(t *testing.T) {

tests := []struct {
desc string
sanMatchers []SubjectAltNameMatcher
sanMatchers []StringMatcher
}{
{
desc: "prefix match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{PrefixMatch: newStringP("i-aint-the-one")},
{PrefixMatch: newStringP("192.168.1.1")},
},
},
{
desc: "suffix match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{SuffixMatch: newStringP("i-aint-the-one")},
{SuffixMatch: newStringP("1::68")},
},
},
{
desc: "regex match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{RegexMatch: regexp.MustCompile(`.*\.examples\.com`)},
{RegexMatch: regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)},
},
},
{
desc: "contains match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{ContainsMatch: newStringP("i-aint-the-one")},
{ContainsMatch: newStringP("2001:db8:1:1::68")},
},
},
{
desc: "exact match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{ExactMatch: newStringP("abcd.test.com")},
{ExactMatch: newStringP("http://golang")},
},
Expand Down Expand Up @@ -211,46 +211,46 @@ func TestMatchingSANExists_Success(t *testing.T) {

tests := []struct {
desc string
sanMatchers []SubjectAltNameMatcher
sanMatchers []StringMatcher
}{
{
desc: "no san matchers",
},
{
desc: "prefix match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{SuffixMatch: newStringP(".co.in")},
{PrefixMatch: newStringP("192.168.1.1")},
{PrefixMatch: newStringP("baz.test")},
},
},
{
desc: "suffix match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{RegexMatch: regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)},
{SuffixMatch: newStringP("192.168.1.1")},
{SuffixMatch: newStringP("@test.com")},
},
},
{
desc: "regex match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{ContainsMatch: newStringP("https://github.com/grpc/grpc-java")},
{RegexMatch: regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`)},
{RegexMatch: regexp.MustCompile(`.*\.test\.com`)},
},
},
{
desc: "contains match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{ExactMatch: newStringP("https://github.com/grpc/grpc-java")},
{ContainsMatch: newStringP("2001:68::db8")},
{ContainsMatch: newStringP("192.0.0")},
},
},
{
desc: "exact match",
sanMatchers: []SubjectAltNameMatcher{
sanMatchers: []StringMatcher{
{PrefixMatch: newStringP("192.168.1.1")},
{ExactMatch: newStringP("https://github.com/grpc/grpc-java")},
{ExactMatch: newStringP("abc.example.com")},
Expand Down
7 changes: 4 additions & 3 deletions xds/internal/balancer/cdsbalancer/cdsbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,14 +278,15 @@ func (b *cdsBalancer) handleSecurityConfig(config *xdsclient.SecurityConfig) err
// could have been non-nil earlier.
b.xdsHI.SetRootCertProvider(rootProvider)
b.xdsHI.SetIdentityCertProvider(identityProvider)
var matchers []xdsinternal.SubjectAltNameMatcher
var matchers []xdsinternal.StringMatcher
for _, m := range config.SubjectAltNameMatchers {
matchers = append(matchers, xdsinternal.SubjectAltNameMatcher{
matchers = append(matchers, xdsinternal.StringMatcher{
ExactMatch: m.ExactMatch,
RegexMatch: m.RegexMatch,
PrefixMatch: m.PrefixMatch,
SuffixMatch: m.SuffixMatch,
RegexMatch: m.RegexMatch,
ContainsMatch: m.ContainsMatch,
IgnoreCase: m.IgnoreCase,
})
}
b.xdsHI.SetSANMatchers(matchers)
Expand Down
10 changes: 8 additions & 2 deletions xds/internal/client/cds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,10 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
DefaultValidationContext: &v3tlspb.CertificateValidationContext{
MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact}},
{
MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact},
IgnoreCase: true,
},
{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}},
{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}},
{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}},
Expand Down Expand Up @@ -715,7 +718,10 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
IdentityInstanceName: identityPluginInstance,
IdentityCertName: identityCertName,
SubjectAltNameMatchers: []StringMatcher{
{ExactMatch: newStringP(sanExact)},
{
ExactMatch: newStringP(sanExact),
IgnoreCase: true,
},
{PrefixMatch: newStringP(sanPrefix)},
{SuffixMatch: newStringP(sanSuffix)},
{RegexMatch: sanRE},
Expand Down
9 changes: 6 additions & 3 deletions xds/internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,15 +338,18 @@ type SecurityConfig struct {
RequireClientCert bool
}

// StringMatcher contains match criteria for matching a string. Since the match
// fields are part of a `oneof` in the corresponding xDS proto, only one of them
// is expected to be set.
// StringMatcher contains match criteria for matching a string.
type StringMatcher struct {
// Since these match fields are part of a `oneof` in the corresponding xDS
// proto, only one of them is expected to be set.
ExactMatch *string
PrefixMatch *string
SuffixMatch *string
RegexMatch *regexp.Regexp
ContainsMatch *string
// If true, indicates the exact/prefix/suffix/contains matching should be
// case insensitive. This has no effect on the regex match.
IgnoreCase bool
}

// ClusterUpdate contains information from a received CDS response, which is of
Expand Down
1 change: 1 addition & 0 deletions xds/internal/client/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ func securityConfigFromCommonTLSContext(common *v3tlspb.CommonTlsContext) (*Secu
default:
return nil, fmt.Errorf("combined validation context has unrecognized string matcher: %+v", m)
}
matcher.IgnoreCase = m.GetIgnoreCase()
matchers = append(matchers, matcher)
}
}
Expand Down

0 comments on commit 2640699

Please sign in to comment.