google.golang.org/grpc@v1.72.2/internal/credentials/xds/handshake_info_test.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package xds
    20  
    21  import (
    22  	"crypto/x509"
    23  	"net"
    24  	"net/netip"
    25  	"net/url"
    26  	"regexp"
    27  	"testing"
    28  
    29  	"google.golang.org/grpc/credentials/tls/certprovider"
    30  	"google.golang.org/grpc/internal/xds/matcher"
    31  )
    32  
    33  type testCertProvider struct {
    34  	certprovider.Provider
    35  }
    36  
    37  func TestDNSMatch(t *testing.T) {
    38  	tests := []struct {
    39  		desc      string
    40  		host      string
    41  		pattern   string
    42  		wantMatch bool
    43  	}{
    44  		{
    45  			desc:      "invalid wildcard 1",
    46  			host:      "aa.example.com",
    47  			pattern:   "*a.example.com",
    48  			wantMatch: false,
    49  		},
    50  		{
    51  			desc:      "invalid wildcard 2",
    52  			host:      "aa.example.com",
    53  			pattern:   "a*.example.com",
    54  			wantMatch: false,
    55  		},
    56  		{
    57  			desc:      "invalid wildcard 3",
    58  			host:      "abc.example.com",
    59  			pattern:   "a*c.example.com",
    60  			wantMatch: false,
    61  		},
    62  		{
    63  			desc:      "wildcard in one of the middle components",
    64  			host:      "abc.test.example.com",
    65  			pattern:   "abc.*.example.com",
    66  			wantMatch: false,
    67  		},
    68  		{
    69  			desc:      "single component wildcard",
    70  			host:      "a.example.com",
    71  			pattern:   "*",
    72  			wantMatch: false,
    73  		},
    74  		{
    75  			desc:      "short host name",
    76  			host:      "a.com",
    77  			pattern:   "*.example.com",
    78  			wantMatch: false,
    79  		},
    80  		{
    81  			desc:      "suffix mismatch",
    82  			host:      "a.notexample.com",
    83  			pattern:   "*.example.com",
    84  			wantMatch: false,
    85  		},
    86  		{
    87  			desc:      "wildcard match across components",
    88  			host:      "sub.test.example.com",
    89  			pattern:   "*.example.com.",
    90  			wantMatch: false,
    91  		},
    92  		{
    93  			desc:      "host doesn't end in period",
    94  			host:      "test.example.com",
    95  			pattern:   "test.example.com.",
    96  			wantMatch: true,
    97  		},
    98  		{
    99  			desc:      "pattern doesn't end in period",
   100  			host:      "test.example.com.",
   101  			pattern:   "test.example.com",
   102  			wantMatch: true,
   103  		},
   104  		{
   105  			desc:      "case insensitive",
   106  			host:      "TEST.EXAMPLE.COM.",
   107  			pattern:   "test.example.com.",
   108  			wantMatch: true,
   109  		},
   110  		{
   111  			desc:      "simple match",
   112  			host:      "test.example.com",
   113  			pattern:   "test.example.com",
   114  			wantMatch: true,
   115  		},
   116  		{
   117  			desc:      "good wildcard",
   118  			host:      "a.example.com",
   119  			pattern:   "*.example.com",
   120  			wantMatch: true,
   121  		},
   122  	}
   123  
   124  	for _, test := range tests {
   125  		t.Run(test.desc, func(t *testing.T) {
   126  			gotMatch := dnsMatch(test.host, test.pattern)
   127  			if gotMatch != test.wantMatch {
   128  				t.Fatalf("dnsMatch(%s, %s) = %v, want %v", test.host, test.pattern, gotMatch, test.wantMatch)
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestMatchingSANExists_FailureCases(t *testing.T) {
   135  	url1, err := url.Parse("http://golang.org")
   136  	if err != nil {
   137  		t.Fatalf("url.Parse() failed: %v", err)
   138  	}
   139  	url2, err := url.Parse("https://github.com/grpc/grpc-go")
   140  	if err != nil {
   141  		t.Fatalf("url.Parse() failed: %v", err)
   142  	}
   143  	inputCert := &x509.Certificate{
   144  		DNSNames:       []string{"foo.bar.example.com", "bar.baz.test.com", "*.example.com"},
   145  		EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"},
   146  		IPAddresses: []net.IP{
   147  			netip.MustParseAddr("192.0.0.1").AsSlice(),
   148  			netip.MustParseAddr("2001:db8::68").AsSlice(),
   149  		},
   150  		URIs: []*url.URL{url1, url2},
   151  	}
   152  
   153  	tests := []struct {
   154  		desc        string
   155  		sanMatchers []matcher.StringMatcher
   156  	}{
   157  		{
   158  			desc: "exact match",
   159  			sanMatchers: []matcher.StringMatcher{
   160  				matcher.StringMatcherForTesting(newStringP("abcd.test.com"), nil, nil, nil, nil, false),
   161  				matcher.StringMatcherForTesting(newStringP("http://golang"), nil, nil, nil, nil, false),
   162  				matcher.StringMatcherForTesting(newStringP("HTTP://GOLANG.ORG"), nil, nil, nil, nil, false),
   163  			},
   164  		},
   165  		{
   166  			desc: "prefix match",
   167  			sanMatchers: []matcher.StringMatcher{
   168  				matcher.StringMatcherForTesting(nil, newStringP("i-aint-the-one"), nil, nil, nil, false),
   169  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   170  				matcher.StringMatcherForTesting(nil, newStringP("FOO.BAR"), nil, nil, nil, false),
   171  			},
   172  		},
   173  		{
   174  			desc: "suffix match",
   175  			sanMatchers: []matcher.StringMatcher{
   176  				matcher.StringMatcherForTesting(nil, nil, newStringP("i-aint-the-one"), nil, nil, false),
   177  				matcher.StringMatcherForTesting(nil, nil, newStringP("1::68"), nil, nil, false),
   178  				matcher.StringMatcherForTesting(nil, nil, newStringP(".COM"), nil, nil, false),
   179  			},
   180  		},
   181  		{
   182  			desc: "regex match",
   183  			sanMatchers: []matcher.StringMatcher{
   184  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.examples\.com`), false),
   185  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   186  			},
   187  		},
   188  		{
   189  			desc: "contains match",
   190  			sanMatchers: []matcher.StringMatcher{
   191  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("i-aint-the-one"), nil, false),
   192  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:db8:1:1::68"), nil, false),
   193  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, false),
   194  			},
   195  		},
   196  	}
   197  
   198  	for _, test := range tests {
   199  		t.Run(test.desc, func(t *testing.T) {
   200  			hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false)
   201  
   202  			if hi.MatchingSANExists(inputCert) {
   203  				t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v succeeded when expected to fail", inputCert, test.sanMatchers)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestMatchingSANExists_Success(t *testing.T) {
   210  	url1, err := url.Parse("http://golang.org")
   211  	if err != nil {
   212  		t.Fatalf("url.Parse() failed: %v", err)
   213  	}
   214  	url2, err := url.Parse("https://github.com/grpc/grpc-go")
   215  	if err != nil {
   216  		t.Fatalf("url.Parse() failed: %v", err)
   217  	}
   218  	inputCert := &x509.Certificate{
   219  		DNSNames:       []string{"baz.test.com", "*.example.com"},
   220  		EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"},
   221  		IPAddresses: []net.IP{
   222  			netip.MustParseAddr("192.0.0.1").AsSlice(),
   223  			netip.MustParseAddr("2001:db8::68").AsSlice(),
   224  		},
   225  		URIs: []*url.URL{url1, url2},
   226  	}
   227  
   228  	tests := []struct {
   229  		desc        string
   230  		sanMatchers []matcher.StringMatcher
   231  	}{
   232  		{
   233  			desc: "no san matchers",
   234  		},
   235  		{
   236  			desc: "exact match dns wildcard",
   237  			sanMatchers: []matcher.StringMatcher{
   238  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   239  				matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
   240  				matcher.StringMatcherForTesting(newStringP("abc.example.com"), nil, nil, nil, nil, false),
   241  			},
   242  		},
   243  		{
   244  			desc: "exact match ignore case",
   245  			sanMatchers: []matcher.StringMatcher{
   246  				matcher.StringMatcherForTesting(newStringP("FOOBAR@EXAMPLE.COM"), nil, nil, nil, nil, true),
   247  			},
   248  		},
   249  		{
   250  			desc: "prefix match",
   251  			sanMatchers: []matcher.StringMatcher{
   252  				matcher.StringMatcherForTesting(nil, nil, newStringP(".co.in"), nil, nil, false),
   253  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   254  				matcher.StringMatcherForTesting(nil, newStringP("baz.test"), nil, nil, nil, false),
   255  			},
   256  		},
   257  		{
   258  			desc: "prefix match ignore case",
   259  			sanMatchers: []matcher.StringMatcher{
   260  				matcher.StringMatcherForTesting(nil, newStringP("BAZ.test"), nil, nil, nil, true),
   261  			},
   262  		},
   263  		{
   264  			desc: "suffix  match",
   265  			sanMatchers: []matcher.StringMatcher{
   266  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   267  				matcher.StringMatcherForTesting(nil, nil, newStringP("192.168.1.1"), nil, nil, false),
   268  				matcher.StringMatcherForTesting(nil, nil, newStringP("@test.com"), nil, nil, false),
   269  			},
   270  		},
   271  		{
   272  			desc: "suffix  match ignore case",
   273  			sanMatchers: []matcher.StringMatcher{
   274  				matcher.StringMatcherForTesting(nil, nil, newStringP("@test.COM"), nil, nil, true),
   275  			},
   276  		},
   277  		{
   278  			desc: "regex match",
   279  			sanMatchers: []matcher.StringMatcher{
   280  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("https://github.com/grpc/grpc-java"), nil, false),
   281  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   282  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.test\.com`), false),
   283  			},
   284  		},
   285  		{
   286  			desc: "contains match",
   287  			sanMatchers: []matcher.StringMatcher{
   288  				matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
   289  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:68::db8"), nil, false),
   290  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("192.0.0"), nil, false),
   291  			},
   292  		},
   293  		{
   294  			desc: "contains match ignore case",
   295  			sanMatchers: []matcher.StringMatcher{
   296  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, true),
   297  			},
   298  		},
   299  	}
   300  
   301  	for _, test := range tests {
   302  		t.Run(test.desc, func(t *testing.T) {
   303  			hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false)
   304  
   305  			if !hi.MatchingSANExists(inputCert) {
   306  				t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v failed when expected to succeed", inputCert, test.sanMatchers)
   307  			}
   308  		})
   309  	}
   310  }
   311  
   312  func newStringP(s string) *string {
   313  	return &s
   314  }
   315  
   316  func TestEqual(t *testing.T) {
   317  	tests := []struct {
   318  		desc      string
   319  		hi1       *HandshakeInfo
   320  		hi2       *HandshakeInfo
   321  		wantMatch bool
   322  	}{
   323  		{
   324  			desc:      "both HandshakeInfo are nil",
   325  			hi1:       nil,
   326  			hi2:       nil,
   327  			wantMatch: true,
   328  		},
   329  		{
   330  			desc:      "one HandshakeInfo is nil",
   331  			hi1:       nil,
   332  			hi2:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   333  			wantMatch: false,
   334  		},
   335  		{
   336  			desc:      "different root providers",
   337  			hi1:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   338  			hi2:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   339  			wantMatch: false,
   340  		},
   341  		{
   342  			desc: "same providers, same SAN matchers",
   343  			hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   344  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   345  			}, false),
   346  			hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   347  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   348  			}, false),
   349  			wantMatch: true,
   350  		},
   351  		{
   352  			desc: "same providers, different SAN matchers",
   353  			hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   354  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   355  			}, false),
   356  			hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   357  				matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
   358  			}, false),
   359  			wantMatch: false,
   360  		},
   361  		{
   362  			desc: "same SAN matchers with different content",
   363  			hi1: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
   364  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   365  			}, false),
   366  			hi2: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
   367  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   368  				matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
   369  			}, false),
   370  			wantMatch: false,
   371  		},
   372  		{
   373  			desc:      "different requireClientCert flags",
   374  			hi1:       NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, true),
   375  			hi2:       NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, false),
   376  			wantMatch: false,
   377  		},
   378  		{
   379  			desc:      "same identity provider, different root provider",
   380  			hi1:       NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false),
   381  			hi2:       NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false),
   382  			wantMatch: false,
   383  		},
   384  		{
   385  			desc:      "different identity provider, same root provider",
   386  			hi1:       NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false),
   387  			hi2:       NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false),
   388  			wantMatch: false,
   389  		},
   390  	}
   391  
   392  	for _, test := range tests {
   393  		t.Run(test.desc, func(t *testing.T) {
   394  			if gotMatch := test.hi1.Equal(test.hi2); gotMatch != test.wantMatch {
   395  				t.Errorf("hi1.Equal(hi2) = %v; wantMatch %v", gotMatch, test.wantMatch)
   396  			}
   397  		})
   398  	}
   399  }