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  }