github.com/cilium/cilium@v1.16.2/pkg/auth/spire/certificate_provider_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package spire 5 6 import ( 7 "crypto/rand" 8 "crypto/rsa" 9 "crypto/tls" 10 "crypto/x509" 11 "crypto/x509/pkix" 12 "math/big" 13 "net/url" 14 "reflect" 15 "testing" 16 "time" 17 18 delegatedidentityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/agent/delegatedidentity/v1" 19 "github.com/spiffe/spire-api-sdk/proto/spire/api/types" 20 21 "github.com/cilium/cilium/pkg/auth/certs" 22 "github.com/cilium/cilium/pkg/identity" 23 "github.com/cilium/cilium/pkg/logging" 24 "github.com/cilium/cilium/pkg/logging/logfields" 25 ) 26 27 var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "spire") // just to avoid nil errors as well as debugging tests 28 29 func TestSpireDelegateClient_NumericIdentityToSNI(t *testing.T) { 30 type args struct { 31 id identity.NumericIdentity 32 } 33 tests := []struct { 34 name string 35 args args 36 want string 37 }{ 38 { 39 name: "convert valid numeric identity", 40 args: args{ 41 id: 1234, 42 }, 43 want: "1234.test.cilium.io", 44 }, 45 } 46 for _, tt := range tests { 47 t.Run(tt.name, func(t *testing.T) { 48 s := &SpireDelegateClient{ 49 cfg: SpireDelegateConfig{ 50 SpiffeTrustDomain: "test.cilium.io", 51 }, 52 log: log, 53 } 54 if got := s.NumericIdentityToSNI(tt.args.id); got != tt.want { 55 t.Errorf("SpireDelegateClient.NumericIdentityToSNI() = %v, want %v", got, tt.want) 56 } 57 }) 58 } 59 } 60 61 func TestSpireDelegateClient_SNIToNumericIdentity(t *testing.T) { 62 type args struct { 63 sni string 64 } 65 tests := []struct { 66 name string 67 args args 68 want identity.NumericIdentity 69 wantErr bool 70 }{ 71 { 72 name: "convert valid SNI", 73 args: args{ 74 sni: "1234.test.cilium.io", 75 }, 76 want: 1234, 77 wantErr: false, 78 }, 79 { 80 name: "error on convert invalid SNI under trust domain", 81 args: args{ 82 sni: "hacker.test.cilium.io", 83 }, 84 want: 0, 85 wantErr: true, 86 }, 87 { 88 name: "error on convert invalid SNI outside trust domain", 89 args: args{ 90 sni: "1234.cilium.example.com", 91 }, 92 want: 0, 93 wantErr: true, 94 }, 95 } 96 for _, tt := range tests { 97 t.Run(tt.name, func(t *testing.T) { 98 s := &SpireDelegateClient{ 99 cfg: SpireDelegateConfig{ 100 SpiffeTrustDomain: "test.cilium.io", 101 }, 102 log: log, 103 } 104 got, err := s.SNIToNumericIdentity(tt.args.sni) 105 if (err != nil) != tt.wantErr { 106 t.Errorf("SpireDelegateClient.SNIToNumericIdentity() error = %v, wantErr %v", err, tt.wantErr) 107 return 108 } 109 if !reflect.DeepEqual(got, tt.want) { 110 t.Errorf("SpireDelegateClient.SNIToNumericIdentity() = %v, want %v", got, tt.want) 111 } 112 }) 113 } 114 } 115 116 func TestSpireDelegateClient_sniToSPIFFEID(t *testing.T) { 117 type args struct { 118 id identity.NumericIdentity 119 } 120 tests := []struct { 121 name string 122 args args 123 want string 124 }{ 125 { 126 name: "convert valid numeric identity", 127 args: args{ 128 id: 1234, 129 }, 130 want: "spiffe://test.cilium.io/identity/1234", 131 }, 132 } 133 for _, tt := range tests { 134 t.Run(tt.name, func(t *testing.T) { 135 s := &SpireDelegateClient{ 136 cfg: SpireDelegateConfig{ 137 SpiffeTrustDomain: "test.cilium.io", 138 }, 139 log: log, 140 } 141 if got := s.sniToSPIFFEID(tt.args.id); got != tt.want { 142 t.Errorf("SpireDelegateClient.sniToSPIFFEID() = %v, want %v", got, tt.want) 143 } 144 }) 145 } 146 } 147 148 func TestSpireDelegateClient_ValidateIdentity(t *testing.T) { 149 urlFor1234, _ := url.Parse("spiffe://test.cilium.io/identity/1234") 150 urlFor9999, _ := url.Parse("spiffe://test.cilium.io/identity/9999") 151 152 type args struct { 153 id identity.NumericIdentity 154 cert *x509.Certificate 155 } 156 tests := []struct { 157 name string 158 args args 159 want bool 160 wantErr bool 161 }{ 162 { 163 name: "validate with correct SPIFFE ID", 164 args: args{ 165 id: 1234, 166 cert: &x509.Certificate{ 167 URIs: []*url.URL{urlFor1234}, 168 }, 169 }, 170 want: true, 171 }, 172 { 173 name: "not validate with incorrect SPIFFE ID", 174 args: args{ 175 id: 1234, 176 cert: &x509.Certificate{ 177 URIs: []*url.URL{urlFor9999}, 178 }, 179 }, 180 want: false, 181 }, 182 { 183 name: "error on validate with incorrect SPIFFE cert", 184 args: args{ 185 id: 1234, 186 cert: &x509.Certificate{ 187 URIs: []*url.URL{urlFor1234, urlFor9999}, 188 }, 189 }, 190 want: false, 191 wantErr: true, 192 }, 193 { 194 name: "error on validate with non SPIFFE cert", 195 args: args{ 196 id: 1234, 197 cert: &x509.Certificate{ 198 DNSNames: []string{"test.cilium.io"}, 199 }, 200 }, 201 want: false, 202 wantErr: true, 203 }, 204 } 205 for _, tt := range tests { 206 t.Run(tt.name, func(t *testing.T) { 207 s := &SpireDelegateClient{ 208 cfg: SpireDelegateConfig{ 209 SpiffeTrustDomain: "test.cilium.io", 210 }, 211 log: log, 212 } 213 got, err := s.ValidateIdentity(tt.args.id, tt.args.cert) 214 if (err != nil) != tt.wantErr { 215 t.Errorf("SpireDelegateClient.ValidateIdentity() error = %v, wantErr %v", err, tt.wantErr) 216 return 217 } 218 if got != tt.want { 219 t.Errorf("SpireDelegateClient.ValidateIdentity() = %v, want %v", got, tt.want) 220 } 221 }) 222 } 223 } 224 225 func TestSpireDelegateClient_GetTrustBundle(t *testing.T) { 226 someTrustBundle := x509.NewCertPool() 227 someTrustBundle.AddCert(&x509.Certificate{ 228 Subject: pkix.Name{ 229 CommonName: "test.cilium.io", 230 }, 231 IsCA: true, 232 }) 233 234 tests := []struct { 235 name string 236 trustBundle *x509.CertPool 237 want *x509.CertPool 238 wantErr bool 239 }{ 240 { 241 name: "get trust bundle", 242 trustBundle: someTrustBundle, 243 want: someTrustBundle, 244 }, 245 { 246 name: "error on no trust bundle", 247 trustBundle: nil, 248 want: nil, 249 wantErr: true, 250 }, 251 } 252 253 for _, tt := range tests { 254 t.Run(tt.name, func(t *testing.T) { 255 s := &SpireDelegateClient{ 256 cfg: SpireDelegateConfig{ 257 SpiffeTrustDomain: "test.cilium.io", 258 }, 259 log: log, 260 trustBundle: tt.trustBundle, 261 } 262 got, err := s.GetTrustBundle() 263 if (err != nil) != tt.wantErr { 264 t.Errorf("SpireDelegateClient.GetTrustBundle() error = %v, wantErr %v", err, tt.wantErr) 265 return 266 } 267 if !reflect.DeepEqual(got, tt.want) { 268 t.Errorf("SpireDelegateClient.GetTrustBundle() = %v, want %v", got, tt.want) 269 } 270 }) 271 } 272 } 273 274 func TestSpireDelegateClient_GetCertificateForIdentity(t *testing.T) { 275 certURL, err := url.Parse("spiffe://spiffe.cilium/identity/1234") 276 if err != nil { 277 t.Fatalf("failed to parse URL: %v", err) 278 } 279 leafKey, err := rsa.GenerateKey(rand.Reader, 512) 280 if err != nil { 281 t.Fatalf("failed to generate leaf key: %v", err) 282 } 283 leafCert := &x509.Certificate{ 284 NotAfter: time.Now().Add(time.Hour), 285 URIs: []*url.URL{certURL}, 286 KeyUsage: x509.KeyUsageDigitalSignature, 287 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 288 SerialNumber: big.NewInt(1), 289 } 290 leafCertBytes, err := x509.CreateCertificate(rand.Reader, leafCert, leafCert, &leafKey.PublicKey, leafKey) 291 if err != nil { 292 t.Fatalf("failed to sign leaf certificate: %v", err) 293 } 294 leafCert, err = x509.ParseCertificate(leafCertBytes) 295 if err != nil { 296 t.Fatalf("failed to parse leaf certificate: %v", err) 297 } 298 leafPKCS8Key, err := x509.MarshalPKCS8PrivateKey(leafKey) 299 if err != nil { 300 t.Fatalf("failed to marshal leaf key: %v", err) 301 } 302 303 svidStore := map[string]*delegatedidentityv1.X509SVIDWithKey{ 304 "spiffe://test.cilium.io/identity/1234": { 305 X509Svid: &types.X509SVID{ 306 Id: &types.SPIFFEID{ 307 TrustDomain: "test.cilium.io", 308 Path: "/identity/1234", 309 }, 310 CertChain: [][]byte{leafCertBytes}, 311 }, 312 X509SvidKey: leafPKCS8Key, 313 }, 314 "spiffe://test.cilium.io/identity/2222": { 315 X509Svid: &types.X509SVID{ 316 Id: &types.SPIFFEID{ 317 TrustDomain: "test.cilium.io", 318 Path: "/identity/2222", 319 }, 320 CertChain: [][]byte{}, 321 }, 322 X509SvidKey: leafPKCS8Key, 323 }, 324 "spiffe://test.cilium.io/identity/3333": { 325 X509Svid: &types.X509SVID{ 326 Id: &types.SPIFFEID{ 327 TrustDomain: "test.cilium.io", 328 Path: "/identity/3333", 329 }, 330 CertChain: [][]byte{leafCertBytes}, 331 }, 332 }, 333 } 334 335 type args struct { 336 id identity.NumericIdentity 337 } 338 tests := []struct { 339 name string 340 args args 341 want *tls.Certificate 342 wantErr bool 343 }{ 344 { 345 name: "get certificate for numeric identity", 346 args: args{ 347 id: 1234, 348 }, 349 want: &tls.Certificate{ 350 Certificate: [][]byte{leafCertBytes}, 351 PrivateKey: leafKey, 352 Leaf: leafCert, 353 }, 354 }, 355 { 356 name: "error on no certificate for numeric identity", 357 args: args{ 358 id: 9999, 359 }, 360 want: nil, 361 wantErr: true, 362 }, 363 { 364 name: "error on no certchain for numeric identity", 365 args: args{ 366 id: 2222, 367 }, 368 want: nil, 369 wantErr: true, 370 }, 371 { 372 name: "error on no private key for numeric identity", 373 args: args{ 374 id: 3333, 375 }, 376 want: nil, 377 wantErr: true, 378 }, 379 } 380 for _, tt := range tests { 381 t.Run(tt.name, func(t *testing.T) { 382 s := &SpireDelegateClient{ 383 cfg: SpireDelegateConfig{ 384 SpiffeTrustDomain: "test.cilium.io", 385 }, 386 log: log, 387 svidStore: svidStore, 388 } 389 got, err := s.GetCertificateForIdentity(tt.args.id) 390 if (err != nil) != tt.wantErr { 391 t.Errorf("SpireDelegateClient.GetCertificateForIdentity() error = %v, wantErr %v", err, tt.wantErr) 392 return 393 } 394 if !reflect.DeepEqual(got, tt.want) { 395 t.Errorf("SpireDelegateClient.GetCertificateForIdentity() = %v, want %v", got, tt.want) 396 } 397 }) 398 } 399 } 400 401 func TestSpireDelegateClient_SubscribeToRotatedIdentities(t *testing.T) { 402 tests := []struct { 403 name string 404 actions []func(t *testing.T, s *SpireDelegateClient) 405 events []certs.CertificateRotationEvent 406 }{ 407 { 408 name: "receive no event on a new ID", 409 actions: []func(t *testing.T, s *SpireDelegateClient){ 410 func(t *testing.T, s *SpireDelegateClient) { 411 s.handleX509SVIDUpdate([]*delegatedidentityv1.X509SVIDWithKey{ 412 { 413 X509Svid: &types.X509SVID{ 414 Id: &types.SPIFFEID{ 415 TrustDomain: "test.cilium.io", 416 Path: "/identity/1234", 417 }, 418 ExpiresAt: time.Now().Add(time.Hour).Unix(), 419 }, 420 }, 421 }) 422 }, 423 }, 424 events: []certs.CertificateRotationEvent{}, 425 }, 426 { 427 name: "receive 1 updated event", 428 actions: []func(t *testing.T, s *SpireDelegateClient){ 429 func(t *testing.T, s *SpireDelegateClient) { 430 s.handleX509SVIDUpdate([]*delegatedidentityv1.X509SVIDWithKey{ 431 { 432 X509Svid: &types.X509SVID{ 433 Id: &types.SPIFFEID{ 434 TrustDomain: "test.cilium.io", 435 Path: "/identity/1234", 436 }, 437 ExpiresAt: time.Now().Add(time.Hour).Unix(), 438 }, 439 }, 440 }) 441 }, 442 func(t *testing.T, s *SpireDelegateClient) { 443 // Update the certificate 444 s.handleX509SVIDUpdate([]*delegatedidentityv1.X509SVIDWithKey{ 445 { 446 X509Svid: &types.X509SVID{ 447 Id: &types.SPIFFEID{ 448 TrustDomain: "test.cilium.io", 449 Path: "/identity/1234", 450 }, 451 ExpiresAt: time.Now().Add(2 * time.Hour).Unix(), 452 }, 453 }, 454 }) 455 }, 456 }, 457 events: []certs.CertificateRotationEvent{ 458 { 459 Identity: 1234, 460 }, 461 }, 462 }, 463 } 464 for _, tt := range tests { 465 t.Run(tt.name, func(t *testing.T) { 466 s := &SpireDelegateClient{ 467 cfg: SpireDelegateConfig{ 468 SpiffeTrustDomain: "test.cilium.io", 469 }, 470 log: log, 471 rotatedIdentitiesChan: make(chan certs.CertificateRotationEvent, 10), 472 svidStore: make(map[string]*delegatedidentityv1.X509SVIDWithKey), 473 } 474 for _, action := range tt.actions { 475 action(t, s) 476 } 477 got := []certs.CertificateRotationEvent{} 478 select { 479 case event := <-s.SubscribeToRotatedIdentities(): 480 got = append(got, event) 481 default: 482 break 483 } 484 485 if !reflect.DeepEqual(got, tt.events) { 486 t.Errorf("SpireDelegateClient.SubscribeToRotatedIdentities() = %v, want %v", got, tt.events) 487 } 488 }) 489 } 490 }