| /* |
| * |
| * Copyright 2020 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package certprovider |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "crypto/x509" |
| "fmt" |
| "io/ioutil" |
| "reflect" |
| "testing" |
| "time" |
| |
| "google.golang.org/grpc/internal/grpctest" |
| "google.golang.org/grpc/testdata" |
| ) |
| |
| const ( |
| fakeProvider1Name = "fake-certificate-provider-1" |
| fakeProvider2Name = "fake-certificate-provider-2" |
| fakeConfig = "my fake config" |
| defaultTestTimeout = 1 * time.Second |
| ) |
| |
| func init() { |
| Register(&fakeProviderBuilder{name: fakeProvider1Name}) |
| Register(&fakeProviderBuilder{name: fakeProvider2Name}) |
| } |
| |
| type s struct { |
| grpctest.Tester |
| } |
| |
| func Test(t *testing.T) { |
| grpctest.RunSubTests(t, s{}) |
| } |
| |
| // fakeProviderBuilder builds new instances of fakeProvider and interprets the |
| // config provided to it as a string. |
| type fakeProviderBuilder struct { |
| name string |
| } |
| |
| func (b *fakeProviderBuilder) Build(StableConfig) Provider { |
| ctx, cancel := context.WithCancel(context.Background()) |
| p := &fakeProvider{ |
| Distributor: NewDistributor(), |
| cancel: cancel, |
| done: make(chan struct{}), |
| kmCh: make(chan *KeyMaterial, 2), |
| } |
| go p.run(ctx) |
| return p |
| } |
| |
| func (b *fakeProviderBuilder) ParseConfig(config interface{}) (StableConfig, error) { |
| s, ok := config.(string) |
| if !ok { |
| return nil, fmt.Errorf("provider %s received bad config %v", b.name, config) |
| } |
| return &fakeStableConfig{config: s}, nil |
| } |
| |
| func (b *fakeProviderBuilder) Name() string { |
| return b.name |
| } |
| |
| type fakeStableConfig struct { |
| config string |
| } |
| |
| func (c *fakeStableConfig) Canonical() []byte { |
| return []byte(c.config) |
| } |
| |
| // fakeProvider is an implementation of the Provider interface which embeds a |
| // Distributor and exposes two channels for the user: |
| // 1. to be notified when the provider is closed |
| // 2. to push new key material into the provider |
| type fakeProvider struct { |
| *Distributor |
| |
| // Used to cancel the run goroutine when the provider is closed. |
| cancel context.CancelFunc |
| // This channel is closed when the provider is closed. Tests should block on |
| // this to make sure the provider is closed. |
| done chan struct{} |
| // Tests can push new key material on this channel, and the provider will |
| // return this on subsequent calls to KeyMaterial(). |
| kmCh chan *KeyMaterial |
| } |
| |
| func (p *fakeProvider) run(ctx context.Context) { |
| for { |
| select { |
| case <-ctx.Done(): |
| return |
| case km := <-p.kmCh: |
| p.Distributor.Set(km, nil) |
| } |
| } |
| } |
| |
| func (p *fakeProvider) Close() { |
| p.cancel() |
| p.Distributor.Stop() |
| } |
| |
| // loadKeyMaterials is a helper to read cert/key files from testdata and convert |
| // them into a KeyMaterial struct. |
| func loadKeyMaterials() (*KeyMaterial, error) { |
| certs, err := tls.LoadX509KeyPair(testdata.Path("server1.pem"), testdata.Path("server1.key")) |
| if err != nil { |
| return nil, err |
| } |
| |
| pemData, err := ioutil.ReadFile(testdata.Path("ca.pem")) |
| if err != nil { |
| return nil, err |
| } |
| roots := x509.NewCertPool() |
| roots.AppendCertsFromPEM(pemData) |
| return &KeyMaterial{Certs: []tls.Certificate{certs}, Roots: roots}, nil |
| } |
| |
| func makeProvider(t *testing.T, name, config string) (Provider, *fakeProvider) { |
| t.Helper() |
| |
| prov, err := GetProvider(name, config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // The store returns a wrappedProvider, which holds a reference to the |
| // actual provider, which in our case in the fakeProvider. |
| wp := prov.(*wrappedProvider) |
| fp := wp.Provider.(*fakeProvider) |
| return prov, fp |
| } |
| |
| // TestStoreWithSingleProvider creates a single provider through the store and |
| // calls methods on it. |
| func (s) TestStoreWithSingleProvider(t *testing.T) { |
| prov, fp := makeProvider(t, fakeProvider1Name, fakeConfig) |
| |
| // Push key materials into the provider. |
| wantKM, err := loadKeyMaterials() |
| if err != nil { |
| t.Fatal(err) |
| } |
| fp.kmCh <- wantKM |
| |
| // Get key materials from the provider and compare it to the ones we pushed |
| // above. |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| gotKM, err := prov.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| // TODO(easwars): Remove all references to reflect.DeepEqual and use |
| // cmp.Equal instead. Currently, the later panics because x509.Certificate |
| // type defines an Equal method, but does not check for nil. This has been |
| // fixed in |
| // https://github.com/golang/go/commit/89865f8ba64ccb27f439cce6daaa37c9aa38f351, |
| // but this is only available starting go1.14. So, once we remove support |
| // for go1.13, we can make the switch. |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Close the provider and retry the KeyMaterial() call, and expect it to |
| // fail with a known error. |
| prov.Close() |
| if _, err := prov.KeyMaterial(ctx); err != errProviderClosed { |
| t.Fatalf("provider.KeyMaterial() = %v, wantErr: %v", err, errProviderClosed) |
| } |
| } |
| |
| // TestStoreWithSingleProviderWithSharing creates multiple instances of the same |
| // type of provider through the store (and expects the store's sharing mechanism |
| // to kick in) and calls methods on it. |
| func (s) TestStoreWithSingleProviderWithSharing(t *testing.T) { |
| prov1, fp1 := makeProvider(t, fakeProvider1Name, fakeConfig) |
| prov2, _ := makeProvider(t, fakeProvider1Name, fakeConfig) |
| |
| // Push key materials into the fake provider1. |
| wantKM, err := loadKeyMaterials() |
| if err != nil { |
| t.Fatal(err) |
| } |
| fp1.kmCh <- wantKM |
| |
| // Get key materials from the fake provider2 and compare it to the ones we |
| // pushed above. |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| gotKM, err := prov2.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Close the provider1 and retry the KeyMaterial() call on prov2, and expect |
| // it to succeed. |
| prov1.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| |
| prov2.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != errProviderClosed { |
| t.Fatalf("provider.KeyMaterial() = %v, wantErr: %v", err, errProviderClosed) |
| } |
| } |
| |
| // TestStoreWithSingleProviderWithoutSharing creates multiple instances of the |
| // same type of provider through the store with different configs. The store |
| // would end up creating different provider instances for these and no sharing |
| // would take place. |
| func (s) TestStoreWithSingleProviderWithoutSharing(t *testing.T) { |
| prov1, fp1 := makeProvider(t, fakeProvider1Name, fakeConfig+"1111") |
| prov2, fp2 := makeProvider(t, fakeProvider1Name, fakeConfig+"2222") |
| |
| // Push the same key materials into the two providers. |
| wantKM, err := loadKeyMaterials() |
| if err != nil { |
| t.Fatal(err) |
| } |
| fp1.kmCh <- wantKM |
| fp2.kmCh <- wantKM |
| |
| // Get key materials from the fake provider1 and compare it to the ones we |
| // pushed above. |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| gotKM, err := prov1.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Get key materials from the fake provider2 and compare it to the ones we |
| // pushed above. |
| gotKM, err = prov2.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Update the key materials used by provider1, and make sure provider2 is |
| // not affected. |
| newKM, err := loadKeyMaterials() |
| if err != nil { |
| t.Fatal(err) |
| } |
| newKM.Roots = nil |
| fp1.kmCh <- newKM |
| |
| gotKM, err = prov2.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Close the provider1 and retry the KeyMaterial() call on prov2, and expect |
| // it to succeed. |
| prov1.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| |
| prov2.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != errProviderClosed { |
| t.Fatalf("provider.KeyMaterial() = %v, wantErr: %v", err, errProviderClosed) |
| } |
| } |
| |
| // TestStoreWithMultipleProviders creates multiple providers of different types |
| // and make sure closing of one does not affect the other. |
| func (s) TestStoreWithMultipleProviders(t *testing.T) { |
| prov1, fp1 := makeProvider(t, fakeProvider1Name, fakeConfig) |
| prov2, fp2 := makeProvider(t, fakeProvider2Name, fakeConfig) |
| |
| // Push key materials into the fake providers. |
| wantKM, err := loadKeyMaterials() |
| if err != nil { |
| t.Fatal(err) |
| } |
| fp1.kmCh <- wantKM |
| fp2.kmCh <- wantKM |
| |
| // Get key materials from the fake provider1 and compare it to the ones we |
| // pushed above. |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| gotKM, err := prov1.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Get key materials from the fake provider2 and compare it to the ones we |
| // pushed above. |
| gotKM, err = prov2.KeyMaterial(ctx) |
| if err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| if !reflect.DeepEqual(gotKM, wantKM) { |
| t.Fatalf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM) |
| } |
| |
| // Close the provider1 and retry the KeyMaterial() call on prov2, and expect |
| // it to succeed. |
| prov1.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != nil { |
| t.Fatalf("provider.KeyMaterial() = %v", err) |
| } |
| |
| prov2.Close() |
| if _, err := prov2.KeyMaterial(ctx); err != errProviderClosed { |
| t.Fatalf("provider.KeyMaterial() = %v, wantErr: %v", err, errProviderClosed) |
| } |
| } |