google.golang.org/grpc@v1.74.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  	"context"
    23  	"crypto/tls"
    24  	"crypto/x509"
    25  	"fmt"
    26  	"net"
    27  	"net/netip"
    28  	"net/url"
    29  	"os"
    30  	"regexp"
    31  	"strings"
    32  	"testing"
    33  	"time"
    34  
    35  	"google.golang.org/grpc/testdata"
    36  
    37  	"google.golang.org/grpc/credentials/tls/certprovider"
    38  	"google.golang.org/grpc/internal/credentials/spiffe"
    39  	"google.golang.org/grpc/internal/xds/matcher"
    40  )
    41  
    42  type testCertProvider struct {
    43  	certprovider.Provider
    44  }
    45  
    46  type testCertProviderWithKeyMaterial struct {
    47  	certprovider.Provider
    48  }
    49  
    50  func TestDNSMatch(t *testing.T) {
    51  	tests := []struct {
    52  		desc      string
    53  		host      string
    54  		pattern   string
    55  		wantMatch bool
    56  	}{
    57  		{
    58  			desc:      "invalid wildcard 1",
    59  			host:      "aa.example.com",
    60  			pattern:   "*a.example.com",
    61  			wantMatch: false,
    62  		},
    63  		{
    64  			desc:      "invalid wildcard 2",
    65  			host:      "aa.example.com",
    66  			pattern:   "a*.example.com",
    67  			wantMatch: false,
    68  		},
    69  		{
    70  			desc:      "invalid wildcard 3",
    71  			host:      "abc.example.com",
    72  			pattern:   "a*c.example.com",
    73  			wantMatch: false,
    74  		},
    75  		{
    76  			desc:      "wildcard in one of the middle components",
    77  			host:      "abc.test.example.com",
    78  			pattern:   "abc.*.example.com",
    79  			wantMatch: false,
    80  		},
    81  		{
    82  			desc:      "single component wildcard",
    83  			host:      "a.example.com",
    84  			pattern:   "*",
    85  			wantMatch: false,
    86  		},
    87  		{
    88  			desc:      "short host name",
    89  			host:      "a.com",
    90  			pattern:   "*.example.com",
    91  			wantMatch: false,
    92  		},
    93  		{
    94  			desc:      "suffix mismatch",
    95  			host:      "a.notexample.com",
    96  			pattern:   "*.example.com",
    97  			wantMatch: false,
    98  		},
    99  		{
   100  			desc:      "wildcard match across components",
   101  			host:      "sub.test.example.com",
   102  			pattern:   "*.example.com.",
   103  			wantMatch: false,
   104  		},
   105  		{
   106  			desc:      "host doesn't end in period",
   107  			host:      "test.example.com",
   108  			pattern:   "test.example.com.",
   109  			wantMatch: true,
   110  		},
   111  		{
   112  			desc:      "pattern doesn't end in period",
   113  			host:      "test.example.com.",
   114  			pattern:   "test.example.com",
   115  			wantMatch: true,
   116  		},
   117  		{
   118  			desc:      "case insensitive",
   119  			host:      "TEST.EXAMPLE.COM.",
   120  			pattern:   "test.example.com.",
   121  			wantMatch: true,
   122  		},
   123  		{
   124  			desc:      "simple match",
   125  			host:      "test.example.com",
   126  			pattern:   "test.example.com",
   127  			wantMatch: true,
   128  		},
   129  		{
   130  			desc:      "good wildcard",
   131  			host:      "a.example.com",
   132  			pattern:   "*.example.com",
   133  			wantMatch: true,
   134  		},
   135  	}
   136  
   137  	for _, test := range tests {
   138  		t.Run(test.desc, func(t *testing.T) {
   139  			gotMatch := dnsMatch(test.host, test.pattern)
   140  			if gotMatch != test.wantMatch {
   141  				t.Fatalf("dnsMatch(%s, %s) = %v, want %v", test.host, test.pattern, gotMatch, test.wantMatch)
   142  			}
   143  		})
   144  	}
   145  }
   146  
   147  func TestMatchingSANExists_FailureCases(t *testing.T) {
   148  	url1, err := url.Parse("http://golang.org")
   149  	if err != nil {
   150  		t.Fatalf("url.Parse() failed: %v", err)
   151  	}
   152  	url2, err := url.Parse("https://github.com/grpc/grpc-go")
   153  	if err != nil {
   154  		t.Fatalf("url.Parse() failed: %v", err)
   155  	}
   156  	inputCert := &x509.Certificate{
   157  		DNSNames:       []string{"foo.bar.example.com", "bar.baz.test.com", "*.example.com"},
   158  		EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"},
   159  		IPAddresses: []net.IP{
   160  			netip.MustParseAddr("192.0.0.1").AsSlice(),
   161  			netip.MustParseAddr("2001:db8::68").AsSlice(),
   162  		},
   163  		URIs: []*url.URL{url1, url2},
   164  	}
   165  
   166  	tests := []struct {
   167  		desc        string
   168  		sanMatchers []matcher.StringMatcher
   169  	}{
   170  		{
   171  			desc: "exact match",
   172  			sanMatchers: []matcher.StringMatcher{
   173  				matcher.StringMatcherForTesting(newStringP("abcd.test.com"), nil, nil, nil, nil, false),
   174  				matcher.StringMatcherForTesting(newStringP("http://golang"), nil, nil, nil, nil, false),
   175  				matcher.StringMatcherForTesting(newStringP("HTTP://GOLANG.ORG"), nil, nil, nil, nil, false),
   176  			},
   177  		},
   178  		{
   179  			desc: "prefix match",
   180  			sanMatchers: []matcher.StringMatcher{
   181  				matcher.StringMatcherForTesting(nil, newStringP("i-aint-the-one"), nil, nil, nil, false),
   182  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   183  				matcher.StringMatcherForTesting(nil, newStringP("FOO.BAR"), nil, nil, nil, false),
   184  			},
   185  		},
   186  		{
   187  			desc: "suffix match",
   188  			sanMatchers: []matcher.StringMatcher{
   189  				matcher.StringMatcherForTesting(nil, nil, newStringP("i-aint-the-one"), nil, nil, false),
   190  				matcher.StringMatcherForTesting(nil, nil, newStringP("1::68"), nil, nil, false),
   191  				matcher.StringMatcherForTesting(nil, nil, newStringP(".COM"), nil, nil, false),
   192  			},
   193  		},
   194  		{
   195  			desc: "regex match",
   196  			sanMatchers: []matcher.StringMatcher{
   197  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.examples\.com`), false),
   198  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   199  			},
   200  		},
   201  		{
   202  			desc: "contains match",
   203  			sanMatchers: []matcher.StringMatcher{
   204  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("i-aint-the-one"), nil, false),
   205  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:db8:1:1::68"), nil, false),
   206  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, false),
   207  			},
   208  		},
   209  	}
   210  
   211  	for _, test := range tests {
   212  		t.Run(test.desc, func(t *testing.T) {
   213  			hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false)
   214  
   215  			if hi.MatchingSANExists(inputCert) {
   216  				t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v succeeded when expected to fail", inputCert, test.sanMatchers)
   217  			}
   218  		})
   219  	}
   220  }
   221  
   222  func TestMatchingSANExists_Success(t *testing.T) {
   223  	url1, err := url.Parse("http://golang.org")
   224  	if err != nil {
   225  		t.Fatalf("url.Parse() failed: %v", err)
   226  	}
   227  	url2, err := url.Parse("https://github.com/grpc/grpc-go")
   228  	if err != nil {
   229  		t.Fatalf("url.Parse() failed: %v", err)
   230  	}
   231  	inputCert := &x509.Certificate{
   232  		DNSNames:       []string{"baz.test.com", "*.example.com"},
   233  		EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"},
   234  		IPAddresses: []net.IP{
   235  			netip.MustParseAddr("192.0.0.1").AsSlice(),
   236  			netip.MustParseAddr("2001:db8::68").AsSlice(),
   237  		},
   238  		URIs: []*url.URL{url1, url2},
   239  	}
   240  
   241  	tests := []struct {
   242  		desc        string
   243  		sanMatchers []matcher.StringMatcher
   244  	}{
   245  		{
   246  			desc: "no san matchers",
   247  		},
   248  		{
   249  			desc: "exact match dns wildcard",
   250  			sanMatchers: []matcher.StringMatcher{
   251  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   252  				matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
   253  				matcher.StringMatcherForTesting(newStringP("abc.example.com"), nil, nil, nil, nil, false),
   254  			},
   255  		},
   256  		{
   257  			desc: "exact match ignore case",
   258  			sanMatchers: []matcher.StringMatcher{
   259  				matcher.StringMatcherForTesting(newStringP("FOOBAR@EXAMPLE.COM"), nil, nil, nil, nil, true),
   260  			},
   261  		},
   262  		{
   263  			desc: "prefix match",
   264  			sanMatchers: []matcher.StringMatcher{
   265  				matcher.StringMatcherForTesting(nil, nil, newStringP(".co.in"), nil, nil, false),
   266  				matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false),
   267  				matcher.StringMatcherForTesting(nil, newStringP("baz.test"), nil, nil, nil, false),
   268  			},
   269  		},
   270  		{
   271  			desc: "prefix match ignore case",
   272  			sanMatchers: []matcher.StringMatcher{
   273  				matcher.StringMatcherForTesting(nil, newStringP("BAZ.test"), nil, nil, nil, true),
   274  			},
   275  		},
   276  		{
   277  			desc: "suffix  match",
   278  			sanMatchers: []matcher.StringMatcher{
   279  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   280  				matcher.StringMatcherForTesting(nil, nil, newStringP("192.168.1.1"), nil, nil, false),
   281  				matcher.StringMatcherForTesting(nil, nil, newStringP("@test.com"), nil, nil, false),
   282  			},
   283  		},
   284  		{
   285  			desc: "suffix  match ignore case",
   286  			sanMatchers: []matcher.StringMatcher{
   287  				matcher.StringMatcherForTesting(nil, nil, newStringP("@test.COM"), nil, nil, true),
   288  			},
   289  		},
   290  		{
   291  			desc: "regex match",
   292  			sanMatchers: []matcher.StringMatcher{
   293  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("https://github.com/grpc/grpc-java"), nil, false),
   294  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false),
   295  				matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.test\.com`), false),
   296  			},
   297  		},
   298  		{
   299  			desc: "contains match",
   300  			sanMatchers: []matcher.StringMatcher{
   301  				matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false),
   302  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:68::db8"), nil, false),
   303  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("192.0.0"), nil, false),
   304  			},
   305  		},
   306  		{
   307  			desc: "contains match ignore case",
   308  			sanMatchers: []matcher.StringMatcher{
   309  				matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, true),
   310  			},
   311  		},
   312  	}
   313  
   314  	for _, test := range tests {
   315  		t.Run(test.desc, func(t *testing.T) {
   316  			hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false)
   317  
   318  			if !hi.MatchingSANExists(inputCert) {
   319  				t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v failed when expected to succeed", inputCert, test.sanMatchers)
   320  			}
   321  		})
   322  	}
   323  }
   324  
   325  func newStringP(s string) *string {
   326  	return &s
   327  }
   328  
   329  func TestEqual(t *testing.T) {
   330  	tests := []struct {
   331  		desc      string
   332  		hi1       *HandshakeInfo
   333  		hi2       *HandshakeInfo
   334  		wantMatch bool
   335  	}{
   336  		{
   337  			desc:      "both HandshakeInfo are nil",
   338  			hi1:       nil,
   339  			hi2:       nil,
   340  			wantMatch: true,
   341  		},
   342  		{
   343  			desc:      "one HandshakeInfo is nil",
   344  			hi1:       nil,
   345  			hi2:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   346  			wantMatch: false,
   347  		},
   348  		{
   349  			desc:      "different root providers",
   350  			hi1:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   351  			hi2:       NewHandshakeInfo(&testCertProvider{}, nil, nil, false),
   352  			wantMatch: false,
   353  		},
   354  		{
   355  			desc: "same providers, same SAN matchers",
   356  			hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   357  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   358  			}, false),
   359  			hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   360  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   361  			}, false),
   362  			wantMatch: true,
   363  		},
   364  		{
   365  			desc: "same providers, different SAN matchers",
   366  			hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   367  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   368  			}, false),
   369  			hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{
   370  				matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
   371  			}, false),
   372  			wantMatch: false,
   373  		},
   374  		{
   375  			desc: "same SAN matchers with different content",
   376  			hi1: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
   377  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   378  			}, false),
   379  			hi2: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{
   380  				matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false),
   381  				matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false),
   382  			}, false),
   383  			wantMatch: false,
   384  		},
   385  		{
   386  			desc:      "different requireClientCert flags",
   387  			hi1:       NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, true),
   388  			hi2:       NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, false),
   389  			wantMatch: false,
   390  		},
   391  		{
   392  			desc:      "same identity provider, different root provider",
   393  			hi1:       NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false),
   394  			hi2:       NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false),
   395  			wantMatch: false,
   396  		},
   397  		{
   398  			desc:      "different identity provider, same root provider",
   399  			hi1:       NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false),
   400  			hi2:       NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false),
   401  			wantMatch: false,
   402  		},
   403  	}
   404  
   405  	for _, test := range tests {
   406  		t.Run(test.desc, func(t *testing.T) {
   407  			if gotMatch := test.hi1.Equal(test.hi2); gotMatch != test.wantMatch {
   408  				t.Errorf("hi1.Equal(hi2) = %v; wantMatch %v", gotMatch, test.wantMatch)
   409  			}
   410  		})
   411  	}
   412  }
   413  
   414  func (p *testCertProviderWithKeyMaterial) KeyMaterial(_ context.Context) (*certprovider.KeyMaterial, error) {
   415  	km := &certprovider.KeyMaterial{}
   416  	spiffeBundleMapContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client_spiffebundle.json"))
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	bundleMap, err := spiffe.BundleMapFromBytes(spiffeBundleMapContents)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  	km.SPIFFEBundleMap = bundleMap
   425  	rootFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/ca.pem"))
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  	trustPool := x509.NewCertPool()
   430  	if !trustPool.AppendCertsFromPEM(rootFileContents) {
   431  		return nil, fmt.Errorf("Failed to parse root certificate")
   432  	}
   433  	km.Roots = trustPool
   434  
   435  	certFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client_spiffe.pem"))
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  	keyFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client.key"))
   440  	if err != nil {
   441  		return nil, err
   442  	}
   443  	cert, err := tls.X509KeyPair(certFileContents, keyFileContents)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	km.Certs = []tls.Certificate{cert}
   448  	return km, nil
   449  }
   450  
   451  func TestBuildVerifyFuncFailures(t *testing.T) {
   452  	tests := []struct {
   453  		desc          string
   454  		peerCertChain [][]byte
   455  		wantErr       string
   456  	}{
   457  		{
   458  			desc:          "invalid x509",
   459  			peerCertChain: [][]byte{[]byte("NOT_A_CERT")},
   460  			wantErr:       "x509: malformed certificate",
   461  		},
   462  		{
   463  			desc: "invalid SPIFFE ID in peer cert",
   464  			// server1.pem doesn't have a valid SPIFFE ID, so attempted to get a
   465  			// root from the SPIFFE Bundle Map will fail
   466  			peerCertChain: loadCert(t, testdata.Path("server1.pem"), testdata.Path("server1.key")),
   467  			wantErr:       "spiffe: could not get spiffe ID from peer leaf cert but verification with spiffe trust map was configure",
   468  		},
   469  	}
   470  	testProvider := testCertProviderWithKeyMaterial{}
   471  	hi := NewHandshakeInfo(&testProvider, &testProvider, nil, true)
   472  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   473  	defer cancel()
   474  	cfg, err := hi.ClientSideTLSConfig(ctx)
   475  	if err != nil {
   476  		t.Fatalf("hi.ClientSideTLSConfig() failed with err %v", err)
   477  	}
   478  	for _, tc := range tests {
   479  		t.Run(tc.desc, func(t *testing.T) {
   480  			err = cfg.VerifyPeerCertificate(tc.peerCertChain, nil)
   481  			if !strings.Contains(err.Error(), tc.wantErr) {
   482  				t.Errorf("VerifyPeerCertificate got err %v, want: %v", err, tc.wantErr)
   483  			}
   484  		})
   485  	}
   486  }
   487  
   488  func loadCert(t *testing.T, certPath, keyPath string) [][]byte {
   489  	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
   490  	if err != nil {
   491  		t.Fatalf("LoadX509KeyPair(%s, %s) failed: %v", certPath, keyPath, err)
   492  	}
   493  	return cert.Certificate
   494  
   495  }