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 }