meshca: remove meshca certificate provider implementation (#4385)

This commit is contained in:
Easwar Swaminathan 2021-05-04 14:38:47 -07:00 коммит произвёл GitHub
Родитель ebd6aba675
Коммит 75497df97f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 0 добавлений и 2232 удалений

Просмотреть файл

@ -1,165 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"crypto/x509"
"encoding/json"
"fmt"
"sync"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/sts"
"google.golang.org/grpc/credentials/tls/certprovider"
"google.golang.org/grpc/internal/backoff"
)
const pluginName = "mesh_ca"
// For overriding in unit tests.
var (
grpcDialFunc = grpc.Dial
backoffFunc = backoff.DefaultExponential.Backoff
)
func init() {
certprovider.Register(newPluginBuilder())
}
func newPluginBuilder() *pluginBuilder {
return &pluginBuilder{clients: make(map[ccMapKey]*refCountedCC)}
}
// Key for the map containing ClientConns to the MeshCA server. Only the server
// name and the STS options (which is used to create call creds) from the plugin
// configuration determine if two configs can share the same ClientConn. Hence
// only those form the key to this map.
type ccMapKey struct {
name string
stsOpts sts.Options
}
// refCountedCC wraps a grpc.ClientConn to MeshCA along with a reference count.
type refCountedCC struct {
cc *grpc.ClientConn
refCnt int
}
// pluginBuilder is an implementation of the certprovider.Builder interface,
// which builds certificate provider instances to get certificates signed from
// the MeshCA.
type pluginBuilder struct {
// A collection of ClientConns to the MeshCA server along with a reference
// count. Provider instances whose config point to the same server name will
// end up sharing the ClientConn.
mu sync.Mutex
clients map[ccMapKey]*refCountedCC
}
// ParseConfig parses the configuration to be passed to the MeshCA plugin
// implementation. Expects the config to be a json.RawMessage which contains a
// serialized JSON representation of the meshca_experimental.GoogleMeshCaConfig
// proto message.
//
// Takes care of sharing the ClientConn to the MeshCA server among
// different plugin instantiations.
func (b *pluginBuilder) ParseConfig(c interface{}) (*certprovider.BuildableConfig, error) {
data, ok := c.(json.RawMessage)
if !ok {
return nil, fmt.Errorf("meshca: unsupported config type: %T", c)
}
cfg, err := pluginConfigFromJSON(data)
if err != nil {
return nil, err
}
return certprovider.NewBuildableConfig(pluginName, cfg.canonical(), func(opts certprovider.BuildOptions) certprovider.Provider {
return b.buildFromConfig(cfg, opts)
}), nil
}
// buildFromConfig builds a certificate provider instance for the given config
// and options. Provider instances are shared wherever possible.
func (b *pluginBuilder) buildFromConfig(cfg *pluginConfig, opts certprovider.BuildOptions) certprovider.Provider {
b.mu.Lock()
defer b.mu.Unlock()
ccmk := ccMapKey{
name: cfg.serverURI,
stsOpts: cfg.stsOpts,
}
rcc, ok := b.clients[ccmk]
if !ok {
// STS call credentials take care of exchanging a locally provisioned
// JWT token for an access token which will be accepted by the MeshCA.
callCreds, err := sts.NewCredentials(cfg.stsOpts)
if err != nil {
logger.Errorf("sts.NewCredentials() failed: %v", err)
return nil
}
// MeshCA is a public endpoint whose certificate is Web-PKI compliant.
// So, we just need to use the system roots to authenticate the MeshCA.
cp, err := x509.SystemCertPool()
if err != nil {
logger.Errorf("x509.SystemCertPool() failed: %v", err)
return nil
}
transportCreds := credentials.NewClientTLSFromCert(cp, "")
cc, err := grpcDialFunc(cfg.serverURI, grpc.WithTransportCredentials(transportCreds), grpc.WithPerRPCCredentials(callCreds))
if err != nil {
logger.Errorf("grpc.Dial(%s) failed: %v", cfg.serverURI, err)
return nil
}
rcc = &refCountedCC{cc: cc}
b.clients[ccmk] = rcc
}
rcc.refCnt++
p := newProviderPlugin(providerParams{
cc: rcc.cc,
cfg: cfg,
opts: opts,
backoff: backoffFunc,
doneFunc: func() {
// The plugin implementation will invoke this function when it is
// being closed, and here we take care of closing the ClientConn
// when there are no more plugins using it. We need to acquire the
// lock before accessing the rcc from the enclosing function.
b.mu.Lock()
defer b.mu.Unlock()
rcc.refCnt--
if rcc.refCnt == 0 {
logger.Infof("Closing grpc.ClientConn to %s", ccmk.name)
rcc.cc.Close()
delete(b.clients, ccmk)
}
},
})
return p
}
// Name returns the MeshCA plugin name.
func (b *pluginBuilder) Name() string {
return pluginName
}

Просмотреть файл

@ -1,177 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"context"
"encoding/json"
"fmt"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/tls/certprovider"
"google.golang.org/grpc/internal/testutils"
)
func overrideHTTPFuncs() func() {
// Directly override the functions which are used to read the zone and
// audience instead of overriding the http.Client.
origReadZone := readZoneFunc
readZoneFunc = func(httpDoer) string { return "test-zone" }
origReadAudience := readAudienceFunc
readAudienceFunc = func(httpDoer) string { return "test-audience" }
return func() {
readZoneFunc = origReadZone
readAudienceFunc = origReadAudience
}
}
func (s) TestBuildSameConfig(t *testing.T) {
defer overrideHTTPFuncs()()
// We will attempt to create `cnt` number of providers. So we create a
// channel of the same size here, even though we expect only one ClientConn
// to be pushed into this channel. This makes sure that even if more than
// one ClientConn ends up being created, the Build() call does not block.
const cnt = 5
ccChan := testutils.NewChannelWithSize(cnt)
// Override the dial func to dial a dummy MeshCA endpoint, and also push the
// returned ClientConn on a channel to be inspected by the test.
origDialFunc := grpcDialFunc
grpcDialFunc = func(string, ...grpc.DialOption) (*grpc.ClientConn, error) {
cc, err := grpc.Dial("dummy-meshca-endpoint", grpc.WithInsecure())
ccChan.Send(cc)
return cc, err
}
defer func() { grpcDialFunc = origDialFunc }()
// Parse a good config to generate a stable config which will be passed to
// invocations of Build().
builder := newPluginBuilder()
buildableConfig, err := builder.ParseConfig(goodConfigFullySpecified)
if err != nil {
t.Fatalf("builder.ParseConfig(%q) failed: %v", goodConfigFullySpecified, err)
}
// Create multiple providers with the same config. All these providers must
// end up sharing the same ClientConn.
providers := []certprovider.Provider{}
for i := 0; i < cnt; i++ {
p, err := buildableConfig.Build(certprovider.BuildOptions{})
if err != nil {
t.Fatalf("Build(%+v) failed: %v", buildableConfig, err)
}
providers = append(providers, p)
}
// Make sure only one ClientConn is created.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
val, err := ccChan.Receive(ctx)
if err != nil {
t.Fatalf("Failed to create ClientConn: %v", err)
}
testCC := val.(*grpc.ClientConn)
// Attempt to read the second ClientConn should timeout.
ctx, cancel = context.WithTimeout(context.Background(), defaultTestShortTimeout)
defer cancel()
if _, err := ccChan.Receive(ctx); err != context.DeadlineExceeded {
t.Fatal("Builder created more than one ClientConn")
}
for _, p := range providers {
p.Close()
}
for {
state := testCC.GetState()
if state == connectivity.Shutdown {
break
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if !testCC.WaitForStateChange(ctx, state) {
t.Fatalf("timeout waiting for clientConn state to change from %s", state)
}
}
}
func (s) TestBuildDifferentConfig(t *testing.T) {
defer overrideHTTPFuncs()()
// We will attempt to create two providers with different configs. So we
// expect two ClientConns to be pushed on to this channel.
const cnt = 2
ccChan := testutils.NewChannelWithSize(cnt)
// Override the dial func to dial a dummy MeshCA endpoint, and also push the
// returned ClientConn on a channel to be inspected by the test.
origDialFunc := grpcDialFunc
grpcDialFunc = func(string, ...grpc.DialOption) (*grpc.ClientConn, error) {
cc, err := grpc.Dial("dummy-meshca-endpoint", grpc.WithInsecure())
ccChan.Send(cc)
return cc, err
}
defer func() { grpcDialFunc = origDialFunc }()
builder := newPluginBuilder()
providers := []certprovider.Provider{}
for i := 0; i < cnt; i++ {
// Copy the good test config and modify the serverURI to make sure that
// a new provider is created for the config.
inputConfig := json.RawMessage(fmt.Sprintf(goodConfigFormatStr, fmt.Sprintf("test-mesh-ca:%d", i)))
buildableConfig, err := builder.ParseConfig(inputConfig)
if err != nil {
t.Fatalf("builder.ParseConfig(%q) failed: %v", inputConfig, err)
}
p, err := buildableConfig.Build(certprovider.BuildOptions{})
if err != nil {
t.Fatalf("Build(%+v) failed: %v", buildableConfig, err)
}
providers = append(providers, p)
}
// Make sure two ClientConns are created.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
for i := 0; i < cnt; i++ {
if _, err := ccChan.Receive(ctx); err != nil {
t.Fatalf("Failed to create ClientConn: %v", err)
}
}
// Close the first provider, and attempt to read key material from the
// second provider. The call to read key material should timeout, but it
// should not return certprovider.errProviderClosed.
providers[0].Close()
ctx, cancel = context.WithTimeout(context.Background(), defaultTestShortTimeout)
defer cancel()
if _, err := providers[1].KeyMaterial(ctx); err != context.DeadlineExceeded {
t.Fatalf("provider.KeyMaterial(ctx) = %v, want contextDeadlineExceeded", err)
}
// Close the second provider to make sure that the leakchecker is happy.
providers[1].Close()
}

Просмотреть файл

@ -1,310 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"path"
"strings"
"time"
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
"github.com/golang/protobuf/ptypes"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/grpc/credentials/sts"
)
const (
// GKE metadata server endpoint.
mdsBaseURI = "http://metadata.google.internal/"
mdsRequestTimeout = 5 * time.Second
// The following are default values used in the interaction with MeshCA.
defaultMeshCaEndpoint = "meshca.googleapis.com"
defaultCallTimeout = 10 * time.Second
defaultCertLifetimeSecs = 86400 // 24h in seconds
defaultCertGraceTimeSecs = 43200 // 12h in seconds
defaultKeyTypeRSA = "RSA"
defaultKeySize = 2048
// The following are default values used in the interaction with STS or
// Secure Token Service, which is used to exchange the JWT token for an
// access token.
defaultSTSEndpoint = "securetoken.googleapis.com"
defaultCloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
defaultRequestedTokenType = "urn:ietf:params:oauth:token-type:access_token"
defaultSubjectTokenType = "urn:ietf:params:oauth:token-type:jwt"
)
// For overriding in unit tests.
var (
makeHTTPDoer = makeHTTPClient
readZoneFunc = readZone
readAudienceFunc = readAudience
)
type pluginConfig struct {
serverURI string
stsOpts sts.Options
callTimeout time.Duration
certLifetime time.Duration
certGraceTime time.Duration
keyType string
keySize int
location string
}
// Type of key to be embedded in CSRs sent to the MeshCA.
const (
keyTypeUnknown = 0
keyTypeRSA = 1
)
// pluginConfigFromJSON parses the provided config in JSON.
//
// For certain values missing in the config, we use default values defined at
// the top of this file.
//
// If the location field or STS audience field is missing, we try talking to the
// GKE Metadata server and try to infer these values. If this attempt does not
// succeed, we let those fields have empty values.
func pluginConfigFromJSON(data json.RawMessage) (*pluginConfig, error) {
// This anonymous struct corresponds to the expected JSON config.
cfgJSON := &struct {
Server json.RawMessage `json:"server,omitempty"` // Expect a v3corepb.ApiConfigSource
CertificateLifetime json.RawMessage `json:"certificate_lifetime,omitempty"` // Expect a durationpb.Duration
RenewalGracePeriod json.RawMessage `json:"renewal_grace_period,omitempty"` // Expect a durationpb.Duration
KeyType int `json:"key_type,omitempty"`
KeySize int `json:"key_size,omitempty"`
Location string `json:"location,omitempty"`
}{}
if err := json.Unmarshal(data, cfgJSON); err != nil {
return nil, fmt.Errorf("meshca: failed to unmarshal config: %v", err)
}
// Further unmarshal fields represented as json.RawMessage in the above
// anonymous struct, and use default values if not specified.
server := &v3corepb.ApiConfigSource{}
if cfgJSON.Server != nil {
if err := protojson.Unmarshal(cfgJSON.Server, server); err != nil {
return nil, fmt.Errorf("meshca: protojson.Unmarshal(%+v) failed: %v", cfgJSON.Server, err)
}
}
certLifetime := &durationpb.Duration{Seconds: defaultCertLifetimeSecs}
if cfgJSON.CertificateLifetime != nil {
if err := protojson.Unmarshal(cfgJSON.CertificateLifetime, certLifetime); err != nil {
return nil, fmt.Errorf("meshca: protojson.Unmarshal(%+v) failed: %v", cfgJSON.CertificateLifetime, err)
}
}
certGraceTime := &durationpb.Duration{Seconds: defaultCertGraceTimeSecs}
if cfgJSON.RenewalGracePeriod != nil {
if err := protojson.Unmarshal(cfgJSON.RenewalGracePeriod, certGraceTime); err != nil {
return nil, fmt.Errorf("meshca: protojson.Unmarshal(%+v) failed: %v", cfgJSON.RenewalGracePeriod, err)
}
}
if api := server.GetApiType(); api != v3corepb.ApiConfigSource_GRPC {
return nil, fmt.Errorf("meshca: server has apiType %s, want %s", api, v3corepb.ApiConfigSource_GRPC)
}
pc := &pluginConfig{
certLifetime: certLifetime.AsDuration(),
certGraceTime: certGraceTime.AsDuration(),
}
gs := server.GetGrpcServices()
if l := len(gs); l != 1 {
return nil, fmt.Errorf("meshca: number of gRPC services in config is %d, expected 1", l)
}
grpcService := gs[0]
googGRPC := grpcService.GetGoogleGrpc()
if googGRPC == nil {
return nil, errors.New("meshca: missing google gRPC service in config")
}
pc.serverURI = googGRPC.GetTargetUri()
if pc.serverURI == "" {
pc.serverURI = defaultMeshCaEndpoint
}
callCreds := googGRPC.GetCallCredentials()
if len(callCreds) == 0 {
return nil, errors.New("meshca: missing call credentials in config")
}
var stsCallCreds *v3corepb.GrpcService_GoogleGrpc_CallCredentials_StsService
for _, cc := range callCreds {
if stsCallCreds = cc.GetStsService(); stsCallCreds != nil {
break
}
}
if stsCallCreds == nil {
return nil, errors.New("meshca: missing STS call credentials in config")
}
if stsCallCreds.GetSubjectTokenPath() == "" {
return nil, errors.New("meshca: missing subjectTokenPath in STS call credentials config")
}
pc.stsOpts = makeStsOptsWithDefaults(stsCallCreds)
var err error
if pc.callTimeout, err = ptypes.Duration(grpcService.GetTimeout()); err != nil {
pc.callTimeout = defaultCallTimeout
}
switch cfgJSON.KeyType {
case keyTypeUnknown, keyTypeRSA:
pc.keyType = defaultKeyTypeRSA
default:
return nil, fmt.Errorf("meshca: unsupported key type: %s, only support RSA keys", pc.keyType)
}
pc.keySize = cfgJSON.KeySize
if pc.keySize == 0 {
pc.keySize = defaultKeySize
}
pc.location = cfgJSON.Location
if pc.location == "" {
pc.location = readZoneFunc(makeHTTPDoer())
}
return pc, nil
}
func (pc *pluginConfig) canonical() []byte {
return []byte(fmt.Sprintf("%s:%s:%s:%s:%s:%s:%d:%s", pc.serverURI, pc.stsOpts, pc.callTimeout, pc.certLifetime, pc.certGraceTime, pc.keyType, pc.keySize, pc.location))
}
func makeStsOptsWithDefaults(stsCallCreds *v3corepb.GrpcService_GoogleGrpc_CallCredentials_StsService) sts.Options {
opts := sts.Options{
TokenExchangeServiceURI: stsCallCreds.GetTokenExchangeServiceUri(),
Resource: stsCallCreds.GetResource(),
Audience: stsCallCreds.GetAudience(),
Scope: stsCallCreds.GetScope(),
RequestedTokenType: stsCallCreds.GetRequestedTokenType(),
SubjectTokenPath: stsCallCreds.GetSubjectTokenPath(),
SubjectTokenType: stsCallCreds.GetSubjectTokenType(),
ActorTokenPath: stsCallCreds.GetActorTokenPath(),
ActorTokenType: stsCallCreds.GetActorTokenType(),
}
// Use sane defaults for unspecified fields.
if opts.TokenExchangeServiceURI == "" {
opts.TokenExchangeServiceURI = defaultSTSEndpoint
}
if opts.Audience == "" {
opts.Audience = readAudienceFunc(makeHTTPDoer())
}
if opts.Scope == "" {
opts.Scope = defaultCloudPlatformScope
}
if opts.RequestedTokenType == "" {
opts.RequestedTokenType = defaultRequestedTokenType
}
if opts.SubjectTokenType == "" {
opts.SubjectTokenType = defaultSubjectTokenType
}
return opts
}
// httpDoer wraps the single method on the http.Client type that we use. This
// helps with overriding in unit tests.
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
func makeHTTPClient() httpDoer {
return &http.Client{Timeout: mdsRequestTimeout}
}
func readMetadata(client httpDoer, uriPath string) (string, error) {
req, err := http.NewRequest("GET", mdsBaseURI+uriPath, nil)
if err != nil {
return "", err
}
req.Header.Add("Metadata-Flavor", "Google")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
dump, err := httputil.DumpRequestOut(req, false)
if err != nil {
logger.Warningf("Failed to dump HTTP request: %v", err)
}
logger.Warningf("Request %q returned status %v", dump, resp.StatusCode)
}
return string(body), err
}
func readZone(client httpDoer) string {
zoneURI := "computeMetadata/v1/instance/zone"
data, err := readMetadata(client, zoneURI)
if err != nil {
logger.Warningf("GET %s failed: %v", path.Join(mdsBaseURI, zoneURI), err)
return ""
}
// The output returned by the metadata server looks like this:
// projects/<PROJECT-NUMBER>/zones/<ZONE>
parts := strings.Split(data, "/")
if len(parts) == 0 {
logger.Warningf("GET %s returned {%s}, does not match expected format {projects/<PROJECT-NUMBER>/zones/<ZONE>}", path.Join(mdsBaseURI, zoneURI))
return ""
}
return parts[len(parts)-1]
}
// readAudience constructs the audience field to be used in the STS request, if
// it is not specified in the plugin configuration.
//
// "identitynamespace:{TRUST_DOMAIN}:{GKE_CLUSTER_URL}" is the format of the
// audience field. When workload identity is enabled on a GCP project, a default
// trust domain is created whose value is "{PROJECT_ID}.svc.id.goog". The format
// of the GKE_CLUSTER_URL is:
// https://container.googleapis.com/v1/projects/{PROJECT_ID}/zones/{ZONE}/clusters/{CLUSTER_NAME}.
func readAudience(client httpDoer) string {
projURI := "computeMetadata/v1/project/project-id"
project, err := readMetadata(client, projURI)
if err != nil {
logger.Warningf("GET %s failed: %v", path.Join(mdsBaseURI, projURI), err)
return ""
}
trustDomain := fmt.Sprintf("%s.svc.id.goog", project)
clusterURI := "computeMetadata/v1/instance/attributes/cluster-name"
cluster, err := readMetadata(client, clusterURI)
if err != nil {
logger.Warningf("GET %s failed: %v", path.Join(mdsBaseURI, clusterURI), err)
return ""
}
zone := readZoneFunc(client)
clusterURL := fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/zones/%s/clusters/%s", project, zone, cluster)
audience := fmt.Sprintf("identitynamespace:%s:%s", trustDomain, clusterURL)
return audience
}

Просмотреть файл

@ -1,375 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/internal/grpctest"
"google.golang.org/grpc/internal/testutils"
)
const (
testProjectID = "test-project-id"
testGKECluster = "test-gke-cluster"
testGCEZone = "test-zone"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
var (
goodConfigFormatStr = `
{
"server": {
"api_type": 2,
"grpc_services": [
{
"googleGrpc": {
"target_uri": %q,
"call_credentials": [
{
"access_token": "foo"
},
{
"sts_service": {
"token_exchange_service_uri": "http://test-sts",
"resource": "test-resource",
"audience": "test-audience",
"scope": "test-scope",
"requested_token_type": "test-requested-token-type",
"subject_token_path": "test-subject-token-path",
"subject_token_type": "test-subject-token-type",
"actor_token_path": "test-actor-token-path",
"actor_token_type": "test-actor-token-type"
}
}
]
},
"timeout": "10s"
}
]
},
"certificate_lifetime": "86400s",
"renewal_grace_period": "43200s",
"key_type": 1,
"key_size": 2048,
"location": "us-west1-b"
}`
goodConfigWithDefaults = json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [
{
"googleGrpc": {
"call_credentials": [
{
"sts_service": {
"subject_token_path": "test-subject-token-path"
}
}
]
},
"timeout": "10s"
}
]
}
}`)
)
var goodConfigFullySpecified = json.RawMessage(fmt.Sprintf(goodConfigFormatStr, "test-meshca"))
// verifyReceivedRequest reads the HTTP request received by the fake client
// (exposed through a channel), and verifies that it matches the expected
// request.
func verifyReceivedRequest(fc *testutils.FakeHTTPClient, wantURI string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
val, err := fc.ReqChan.Receive(ctx)
if err != nil {
return err
}
gotReq := val.(*http.Request)
if gotURI := gotReq.URL.String(); gotURI != wantURI {
return fmt.Errorf("request contains URL %q want %q", gotURI, wantURI)
}
if got, want := gotReq.Header.Get("Metadata-Flavor"), "Google"; got != want {
return fmt.Errorf("request contains flavor %q want %q", got, want)
}
return nil
}
// TestParseConfigSuccessFullySpecified tests the case where the config is fully
// specified and no defaults are required.
func (s) TestParseConfigSuccessFullySpecified(t *testing.T) {
wantConfig := "test-meshca:http://test-sts:test-resource:test-audience:test-scope:test-requested-token-type:test-subject-token-path:test-subject-token-type:test-actor-token-path:test-actor-token-type:10s:24h0m0s:12h0m0s:RSA:2048:us-west1-b"
cfg, err := pluginConfigFromJSON(goodConfigFullySpecified)
if err != nil {
t.Fatalf("pluginConfigFromJSON(%q) failed: %v", goodConfigFullySpecified, err)
}
gotConfig := cfg.canonical()
if diff := cmp.Diff(wantConfig, string(gotConfig)); diff != "" {
t.Errorf("pluginConfigFromJSON(%q) returned config does not match expected (-want +got):\n%s", string(goodConfigFullySpecified), diff)
}
}
// TestParseConfigSuccessWithDefaults tests cases where the config is not fully
// specified, and we end up using some sane defaults.
func (s) TestParseConfigSuccessWithDefaults(t *testing.T) {
wantConfig := fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s",
"meshca.googleapis.com", // Mesh CA Server URI.
"securetoken.googleapis.com", // STS Server URI.
"", // STS Resource Name.
"identitynamespace:test-project-id.svc.id.goog:https://container.googleapis.com/v1/projects/test-project-id/zones/test-zone/clusters/test-gke-cluster", // STS Audience.
"https://www.googleapis.com/auth/cloud-platform", // STS Scope.
"urn:ietf:params:oauth:token-type:access_token", // STS requested token type.
"test-subject-token-path", // STS subject token path.
"urn:ietf:params:oauth:token-type:jwt", // STS subject token type.
"", // STS actor token path.
"", // STS actor token type.
"10s", // Call timeout.
"24h0m0s", // Cert life time.
"12h0m0s", // Cert grace time.
"RSA", // Key type
"2048", // Key size
"test-zone", // Zone
)
// We expect the config parser to make four HTTP requests and receive four
// responses. Hence we setup the request and response channels in the fake
// client with appropriate buffer size.
fc := &testutils.FakeHTTPClient{
ReqChan: testutils.NewChannelWithSize(4),
RespChan: testutils.NewChannelWithSize(4),
}
// Set up the responses to be delivered to the config parser by the fake
// client. The config parser expects responses with project_id,
// gke_cluster_id and gce_zone. The zone is read twice, once as part of
// reading the STS audience and once to get location metadata.
fc.RespChan.Send(&http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(testProjectID))),
})
fc.RespChan.Send(&http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(testGKECluster))),
})
fc.RespChan.Send(&http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(fmt.Sprintf("projects/%s/zones/%s", testProjectID, testGCEZone)))),
})
fc.RespChan.Send(&http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(fmt.Sprintf("projects/%s/zones/%s", testProjectID, testGCEZone)))),
})
// Override the http.Client with our fakeClient.
origMakeHTTPDoer := makeHTTPDoer
makeHTTPDoer = func() httpDoer { return fc }
defer func() { makeHTTPDoer = origMakeHTTPDoer }()
// Spawn a goroutine to verify the HTTP requests sent out as part of the
// config parsing.
errCh := make(chan error, 1)
go func() {
if err := verifyReceivedRequest(fc, "http://metadata.google.internal/computeMetadata/v1/project/project-id"); err != nil {
errCh <- err
return
}
if err := verifyReceivedRequest(fc, "http://metadata.google.internal/computeMetadata/v1/instance/attributes/cluster-name"); err != nil {
errCh <- err
return
}
if err := verifyReceivedRequest(fc, "http://metadata.google.internal/computeMetadata/v1/instance/zone"); err != nil {
errCh <- err
return
}
errCh <- nil
}()
cfg, err := pluginConfigFromJSON(goodConfigWithDefaults)
if err != nil {
t.Fatalf("pluginConfigFromJSON(%q) failed: %v", goodConfigWithDefaults, err)
}
gotConfig := cfg.canonical()
if diff := cmp.Diff(wantConfig, string(gotConfig)); diff != "" {
t.Errorf("builder.ParseConfig(%q) returned config does not match expected (-want +got):\n%s", goodConfigWithDefaults, diff)
}
if err := <-errCh; err != nil {
t.Fatal(err)
}
}
// TestParseConfigFailureCases tests several invalid configs which all result in
// config parsing failures.
func (s) TestParseConfigFailureCases(t *testing.T) {
tests := []struct {
desc string
inputConfig json.RawMessage
wantErr string
}{
{
desc: "invalid JSON",
inputConfig: json.RawMessage(`bad bad json`),
wantErr: "failed to unmarshal config",
},
{
desc: "bad apiType",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 1
}
}`),
wantErr: "server has apiType REST, want GRPC",
},
{
desc: "no grpc services",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2
}
}`),
wantErr: "number of gRPC services in config is 0, expected 1",
},
{
desc: "too many grpc services",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [{}, {}]
}
}`),
wantErr: "number of gRPC services in config is 2, expected 1",
},
{
desc: "missing google grpc service",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [
{
"envoyGrpc": {}
}
]
}
}`),
wantErr: "missing google gRPC service in config",
},
{
desc: "missing call credentials",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [
{
"googleGrpc": {
"target_uri": "foo"
}
}
]
}
}`),
wantErr: "missing call credentials in config",
},
{
desc: "missing STS call credentials",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [
{
"googleGrpc": {
"target_uri": "foo",
"call_credentials": [
{
"access_token": "foo"
}
]
}
}
]
}
}`),
wantErr: "missing STS call credentials in config",
},
{
desc: "with no defaults",
inputConfig: json.RawMessage(`
{
"server": {
"api_type": 2,
"grpc_services": [
{
"googleGrpc": {
"target_uri": "foo",
"call_credentials": [
{
"sts_service": {}
}
]
}
}
]
}
}`),
wantErr: "missing subjectTokenPath in STS call credentials config",
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
cfg, err := pluginConfigFromJSON(test.inputConfig)
if err == nil {
t.Fatalf("pluginConfigFromJSON(%q) = %v, expected to return error (%v)", test.inputConfig, string(cfg.canonical()), test.wantErr)
}
if !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("builder.ParseConfig(%q) = (%v), want error (%v)", test.inputConfig, err, test.wantErr)
}
})
}
}

Просмотреть файл

@ -1,276 +0,0 @@
// Copyright 2019 Istio Authors. All Rights Reserved.
//
// 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: istio/google/security/meshca/v1/meshca.proto
package google_security_meshca_v1
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// Certificate request message.
type MeshCertificateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The request ID must be a valid UUID with the exception that zero UUID is
// not supported (00000000-0000-0000-0000-000000000000).
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
// PEM-encoded certificate request.
Csr string `protobuf:"bytes,2,opt,name=csr,proto3" json:"csr,omitempty"`
// Optional: requested certificate validity period.
Validity *durationpb.Duration `protobuf:"bytes,3,opt,name=validity,proto3" json:"validity,omitempty"` // Reserved 4
}
func (x *MeshCertificateRequest) Reset() {
*x = MeshCertificateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_istio_google_security_meshca_v1_meshca_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MeshCertificateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MeshCertificateRequest) ProtoMessage() {}
func (x *MeshCertificateRequest) ProtoReflect() protoreflect.Message {
mi := &file_istio_google_security_meshca_v1_meshca_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MeshCertificateRequest.ProtoReflect.Descriptor instead.
func (*MeshCertificateRequest) Descriptor() ([]byte, []int) {
return file_istio_google_security_meshca_v1_meshca_proto_rawDescGZIP(), []int{0}
}
func (x *MeshCertificateRequest) GetRequestId() string {
if x != nil {
return x.RequestId
}
return ""
}
func (x *MeshCertificateRequest) GetCsr() string {
if x != nil {
return x.Csr
}
return ""
}
func (x *MeshCertificateRequest) GetValidity() *durationpb.Duration {
if x != nil {
return x.Validity
}
return nil
}
// Certificate response message.
type MeshCertificateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// PEM-encoded certificate chain.
// Leaf cert is element '0'. Root cert is element 'n'.
CertChain []string `protobuf:"bytes,1,rep,name=cert_chain,json=certChain,proto3" json:"cert_chain,omitempty"`
}
func (x *MeshCertificateResponse) Reset() {
*x = MeshCertificateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_istio_google_security_meshca_v1_meshca_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MeshCertificateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MeshCertificateResponse) ProtoMessage() {}
func (x *MeshCertificateResponse) ProtoReflect() protoreflect.Message {
mi := &file_istio_google_security_meshca_v1_meshca_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MeshCertificateResponse.ProtoReflect.Descriptor instead.
func (*MeshCertificateResponse) Descriptor() ([]byte, []int) {
return file_istio_google_security_meshca_v1_meshca_proto_rawDescGZIP(), []int{1}
}
func (x *MeshCertificateResponse) GetCertChain() []string {
if x != nil {
return x.CertChain
}
return nil
}
var File_istio_google_security_meshca_v1_meshca_proto protoreflect.FileDescriptor
var file_istio_google_security_meshca_v1_meshca_proto_rawDesc = []byte{
0x0a, 0x2c, 0x69, 0x73, 0x74, 0x69, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x73,
0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x6d, 0x65, 0x73, 0x68, 0x63, 0x61, 0x2f, 0x76,
0x31, 0x2f, 0x6d, 0x65, 0x73, 0x68, 0x63, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e,
0x6d, 0x65, 0x73, 0x68, 0x63, 0x61, 0x2e, 0x76, 0x31, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x01, 0x0a, 0x16, 0x4d, 0x65,
0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x63, 0x73, 0x72, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74,
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x22, 0x38, 0x0a, 0x17,
0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x65, 0x72, 0x74, 0x5f,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x65, 0x72,
0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x32, 0x96, 0x01, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x68, 0x43,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x7c, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x63, 0x61, 0x2e,
0x76, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x6d, 0x65, 0x73, 0x68,
0x63, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
0x2e, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x73, 0x65,
0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x63, 0x61, 0x2e, 0x76, 0x31,
0x42, 0x0b, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_istio_google_security_meshca_v1_meshca_proto_rawDescOnce sync.Once
file_istio_google_security_meshca_v1_meshca_proto_rawDescData = file_istio_google_security_meshca_v1_meshca_proto_rawDesc
)
func file_istio_google_security_meshca_v1_meshca_proto_rawDescGZIP() []byte {
file_istio_google_security_meshca_v1_meshca_proto_rawDescOnce.Do(func() {
file_istio_google_security_meshca_v1_meshca_proto_rawDescData = protoimpl.X.CompressGZIP(file_istio_google_security_meshca_v1_meshca_proto_rawDescData)
})
return file_istio_google_security_meshca_v1_meshca_proto_rawDescData
}
var file_istio_google_security_meshca_v1_meshca_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_istio_google_security_meshca_v1_meshca_proto_goTypes = []interface{}{
(*MeshCertificateRequest)(nil), // 0: google.security.meshca.v1.MeshCertificateRequest
(*MeshCertificateResponse)(nil), // 1: google.security.meshca.v1.MeshCertificateResponse
(*durationpb.Duration)(nil), // 2: google.protobuf.Duration
}
var file_istio_google_security_meshca_v1_meshca_proto_depIdxs = []int32{
2, // 0: google.security.meshca.v1.MeshCertificateRequest.validity:type_name -> google.protobuf.Duration
0, // 1: google.security.meshca.v1.MeshCertificateService.CreateCertificate:input_type -> google.security.meshca.v1.MeshCertificateRequest
1, // 2: google.security.meshca.v1.MeshCertificateService.CreateCertificate:output_type -> google.security.meshca.v1.MeshCertificateResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_istio_google_security_meshca_v1_meshca_proto_init() }
func file_istio_google_security_meshca_v1_meshca_proto_init() {
if File_istio_google_security_meshca_v1_meshca_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_istio_google_security_meshca_v1_meshca_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MeshCertificateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_istio_google_security_meshca_v1_meshca_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MeshCertificateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_istio_google_security_meshca_v1_meshca_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_istio_google_security_meshca_v1_meshca_proto_goTypes,
DependencyIndexes: file_istio_google_security_meshca_v1_meshca_proto_depIdxs,
MessageInfos: file_istio_google_security_meshca_v1_meshca_proto_msgTypes,
}.Build()
File_istio_google_security_meshca_v1_meshca_proto = out.File
file_istio_google_security_meshca_v1_meshca_proto_rawDesc = nil
file_istio_google_security_meshca_v1_meshca_proto_goTypes = nil
file_istio_google_security_meshca_v1_meshca_proto_depIdxs = nil
}

Просмотреть файл

@ -1,110 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.1.0
// - protoc v3.14.0
// source: istio/google/security/meshca/v1/meshca.proto
package google_security_meshca_v1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// MeshCertificateServiceClient is the client API for MeshCertificateService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MeshCertificateServiceClient interface {
// Using provided CSR, returns a signed certificate that represents a GCP
// service account identity.
CreateCertificate(ctx context.Context, in *MeshCertificateRequest, opts ...grpc.CallOption) (*MeshCertificateResponse, error)
}
type meshCertificateServiceClient struct {
cc grpc.ClientConnInterface
}
func NewMeshCertificateServiceClient(cc grpc.ClientConnInterface) MeshCertificateServiceClient {
return &meshCertificateServiceClient{cc}
}
func (c *meshCertificateServiceClient) CreateCertificate(ctx context.Context, in *MeshCertificateRequest, opts ...grpc.CallOption) (*MeshCertificateResponse, error) {
out := new(MeshCertificateResponse)
err := c.cc.Invoke(ctx, "/google.security.meshca.v1.MeshCertificateService/CreateCertificate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MeshCertificateServiceServer is the server API for MeshCertificateService service.
// All implementations must embed UnimplementedMeshCertificateServiceServer
// for forward compatibility
type MeshCertificateServiceServer interface {
// Using provided CSR, returns a signed certificate that represents a GCP
// service account identity.
CreateCertificate(context.Context, *MeshCertificateRequest) (*MeshCertificateResponse, error)
mustEmbedUnimplementedMeshCertificateServiceServer()
}
// UnimplementedMeshCertificateServiceServer must be embedded to have forward compatible implementations.
type UnimplementedMeshCertificateServiceServer struct {
}
func (UnimplementedMeshCertificateServiceServer) CreateCertificate(context.Context, *MeshCertificateRequest) (*MeshCertificateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateCertificate not implemented")
}
func (UnimplementedMeshCertificateServiceServer) mustEmbedUnimplementedMeshCertificateServiceServer() {
}
// UnsafeMeshCertificateServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MeshCertificateServiceServer will
// result in compilation errors.
type UnsafeMeshCertificateServiceServer interface {
mustEmbedUnimplementedMeshCertificateServiceServer()
}
func RegisterMeshCertificateServiceServer(s grpc.ServiceRegistrar, srv MeshCertificateServiceServer) {
s.RegisterService(&MeshCertificateService_ServiceDesc, srv)
}
func _MeshCertificateService_CreateCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MeshCertificateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MeshCertificateServiceServer).CreateCertificate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/google.security.meshca.v1.MeshCertificateService/CreateCertificate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MeshCertificateServiceServer).CreateCertificate(ctx, req.(*MeshCertificateRequest))
}
return interceptor(ctx, in, info, handler)
}
// MeshCertificateService_ServiceDesc is the grpc.ServiceDesc for MeshCertificateService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MeshCertificateService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "google.security.meshca.v1.MeshCertificateService",
HandlerType: (*MeshCertificateServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CreateCertificate",
Handler: _MeshCertificateService_CreateCertificate_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "istio/google/security/meshca/v1/meshca.proto",
}

Просмотреть файл

@ -1,36 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"fmt"
"google.golang.org/grpc/grpclog"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
)
const prefix = "[%p] "
var logger = grpclog.Component("meshca")
func prefixLogger(p *providerPlugin) *internalgrpclog.PrefixLogger {
return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(prefix, p))
}

Просмотреть файл

@ -1,289 +0,0 @@
// +build go1.13
/*
*
* 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 meshca provides an implementation of the Provider interface which
// communicates with MeshCA to get certificates signed.
package meshca
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"time"
durationpb "github.com/golang/protobuf/ptypes/duration"
"github.com/google/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/tls/certprovider"
meshgrpc "google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1"
meshpb "google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/metadata"
)
// In requests sent to the MeshCA, we add a metadata header with this key and
// the value being the GCE zone in which the workload is running in.
const locationMetadataKey = "x-goog-request-params"
// For overriding from unit tests.
var newDistributorFunc = func() distributor { return certprovider.NewDistributor() }
// distributor wraps the methods on certprovider.Distributor which are used by
// the plugin. This is very useful in tests which need to know exactly when the
// plugin updates its key material.
type distributor interface {
KeyMaterial(ctx context.Context) (*certprovider.KeyMaterial, error)
Set(km *certprovider.KeyMaterial, err error)
Stop()
}
// providerPlugin is an implementation of the certprovider.Provider interface,
// which gets certificates signed by communicating with the MeshCA.
type providerPlugin struct {
distributor // Holds the key material.
cancel context.CancelFunc
cc *grpc.ClientConn // Connection to MeshCA server.
cfg *pluginConfig // Plugin configuration.
opts certprovider.BuildOptions // Key material options.
logger *grpclog.PrefixLogger // Plugin instance specific prefix.
backoff func(int) time.Duration // Exponential backoff.
doneFunc func() // Notify the builder when done.
}
// providerParams wraps params passed to the provider plugin at creation time.
type providerParams struct {
// This ClientConn to the MeshCA server is owned by the builder.
cc *grpc.ClientConn
cfg *pluginConfig
opts certprovider.BuildOptions
backoff func(int) time.Duration
doneFunc func()
}
func newProviderPlugin(params providerParams) *providerPlugin {
ctx, cancel := context.WithCancel(context.Background())
p := &providerPlugin{
cancel: cancel,
cc: params.cc,
cfg: params.cfg,
opts: params.opts,
backoff: params.backoff,
doneFunc: params.doneFunc,
distributor: newDistributorFunc(),
}
p.logger = prefixLogger((p))
p.logger.Infof("plugin created")
go p.run(ctx)
return p
}
func (p *providerPlugin) Close() {
p.logger.Infof("plugin closed")
p.Stop() // Stop the embedded distributor.
p.cancel()
p.doneFunc()
}
// run is a long running goroutine which periodically sends out CSRs to the
// MeshCA, and updates the underlying Distributor with the new key material.
func (p *providerPlugin) run(ctx context.Context) {
// We need to start fetching key material right away. The next attempt will
// be triggered by the timer firing.
for {
certValidity, err := p.updateKeyMaterial(ctx)
if err != nil {
return
}
// We request a certificate with the configured validity duration (which
// is usually twice as much as the grace period). But the server is free
// to return a certificate with whatever validity time it deems right.
refreshAfter := p.cfg.certGraceTime
if refreshAfter > certValidity {
// The default value of cert grace time is half that of the default
// cert validity time. So here, when we have to use a non-default
// cert life time, we will set the grace time again to half that of
// the validity time.
refreshAfter = certValidity / 2
}
timer := time.NewTimer(refreshAfter)
select {
case <-ctx.Done():
return
case <-timer.C:
}
}
}
// updateKeyMaterial generates a CSR and attempts to get it signed from the
// MeshCA. It retries with an exponential backoff till it succeeds or the
// deadline specified in ctx expires. Once it gets the CSR signed from the
// MeshCA, it updates the Distributor with the new key material.
//
// It returns the amount of time the new certificate is valid for.
func (p *providerPlugin) updateKeyMaterial(ctx context.Context) (time.Duration, error) {
client := meshgrpc.NewMeshCertificateServiceClient(p.cc)
retries := 0
for {
if ctx.Err() != nil {
return 0, ctx.Err()
}
if retries != 0 {
bi := p.backoff(retries)
p.logger.Warningf("Backing off for %s before attempting the next CreateCertificate() request", bi)
timer := time.NewTimer(bi)
select {
case <-timer.C:
case <-ctx.Done():
return 0, ctx.Err()
}
}
retries++
privKey, err := rsa.GenerateKey(rand.Reader, p.cfg.keySize)
if err != nil {
p.logger.Warningf("RSA key generation failed: %v", err)
continue
}
// We do not set any fields in the CSR (we use an empty
// x509.CertificateRequest as the template) because the MeshCA discards
// them anyways, and uses the workload identity from the access token
// that we present (as part of the STS call creds).
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{}, crypto.PrivateKey(privKey))
if err != nil {
p.logger.Warningf("CSR creation failed: %v", err)
continue
}
csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
// Send out the CSR with a call timeout and location metadata, as
// specified in the plugin configuration.
req := &meshpb.MeshCertificateRequest{
RequestId: uuid.New().String(),
Csr: string(csrPEM),
Validity: &durationpb.Duration{Seconds: int64(p.cfg.certLifetime / time.Second)},
}
p.logger.Debugf("Sending CreateCertificate() request: %v", req)
callCtx, ctxCancel := context.WithTimeout(context.Background(), p.cfg.callTimeout)
callCtx = metadata.NewOutgoingContext(callCtx, metadata.Pairs(locationMetadataKey, p.cfg.location))
resp, err := client.CreateCertificate(callCtx, req)
if err != nil {
p.logger.Warningf("CreateCertificate request failed: %v", err)
ctxCancel()
continue
}
ctxCancel()
// The returned cert chain must contain more than one cert. Leaf cert is
// element '0', while root cert is element 'n', and the intermediate
// entries form the chain from the root to the leaf.
certChain := resp.GetCertChain()
if l := len(certChain); l <= 1 {
p.logger.Errorf("Received certificate chain contains %d certificates, need more than one", l)
continue
}
// We need to explicitly parse the PEM cert contents as an
// x509.Certificate to read the certificate validity period. We use this
// to decide when to refresh the cert. Even though the call to
// tls.X509KeyPair actually parses the PEM contents into an
// x509.Certificate, it does not store that in the `Leaf` field. See:
// https://golang.org/pkg/crypto/tls/#X509KeyPair.
identity, intermediates, roots, err := parseCertChain(certChain)
if err != nil {
p.logger.Errorf(err.Error())
continue
}
_, err = identity.Verify(x509.VerifyOptions{
Intermediates: intermediates,
Roots: roots,
})
if err != nil {
p.logger.Errorf("Certificate verification failed for return certChain: %v", err)
continue
}
key := x509.MarshalPKCS1PrivateKey(privKey)
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: key})
certPair, err := tls.X509KeyPair([]byte(certChain[0]), keyPEM)
if err != nil {
p.logger.Errorf("Failed to create x509 key pair: %v", err)
continue
}
// At this point, the received response has been deemed good.
retries = 0
// All certs signed by the MeshCA roll up to the same root. And treating
// the last element of the returned chain as the root is the only
// supported option to get the root certificate. So, we ignore the
// options specified in the call to Build(), which contain certificate
// name and whether the caller is interested in identity or root cert.
p.Set(&certprovider.KeyMaterial{Certs: []tls.Certificate{certPair}, Roots: roots}, nil)
return time.Until(identity.NotAfter), nil
}
}
// ParseCertChain parses the result returned by the MeshCA which consists of a
// list of PEM encoded certs. The first element in the list is the leaf or
// identity cert, while the last element is the root, and everything in between
// form the chain of trust.
//
// Caller needs to make sure that certChain has at least two elements.
func parseCertChain(certChain []string) (*x509.Certificate, *x509.CertPool, *x509.CertPool, error) {
identity, err := parseCert([]byte(certChain[0]))
if err != nil {
return nil, nil, nil, err
}
intermediates := x509.NewCertPool()
for _, cert := range certChain[1 : len(certChain)-1] {
i, err := parseCert([]byte(cert))
if err != nil {
return nil, nil, nil, err
}
intermediates.AddCert(i)
}
roots := x509.NewCertPool()
root, err := parseCert([]byte(certChain[len(certChain)-1]))
if err != nil {
return nil, nil, nil, err
}
roots.AddCert(root)
return identity, intermediates, roots, nil
}
func parseCert(certPEM []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, fmt.Errorf("failed to decode received PEM data: %v", certPEM)
}
return x509.ParseCertificate(block.Bytes)
}

Просмотреть файл

@ -1,459 +0,0 @@
// +build go1.13
/*
*
* 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 meshca
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net"
"reflect"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/tls/certprovider"
meshgrpc "google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1"
meshpb "google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1"
"google.golang.org/grpc/internal/testutils"
)
const (
// Used when waiting for something that is expected to *not* happen.
defaultTestShortTimeout = 10 * time.Millisecond
defaultTestTimeout = 5 * time.Second
defaultTestCertLife = time.Hour
shortTestCertLife = 2 * time.Second
maxErrCount = 2
)
// fakeCA provides a very simple fake implementation of the certificate signing
// service as exported by the MeshCA.
type fakeCA struct {
meshgrpc.UnimplementedMeshCertificateServiceServer
withErrors bool // Whether the CA returns errors to begin with.
withShortLife bool // Whether to create certs with short lifetime
ccChan *testutils.Channel // Channel to get notified about CreateCertificate calls.
errors int // Error count.
key *rsa.PrivateKey // Private key of CA.
cert *x509.Certificate // Signing certificate.
certPEM []byte // PEM encoding of signing certificate.
}
// Returns a new instance of the fake Mesh CA. It generates a new RSA key and a
// self-signed certificate which will be used to sign CSRs received in incoming
// requests.
// withErrors controls whether the fake returns errors before succeeding, while
// withShortLife controls whether the fake returns certs with very small
// lifetimes (to test plugin refresh behavior). Every time a CreateCertificate()
// call succeeds, an event is pushed on the ccChan.
func newFakeMeshCA(ccChan *testutils.Channel, withErrors, withShortLife bool) (*fakeCA, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, fmt.Errorf("RSA key generation failed: %v", err)
}
now := time.Now()
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "my-fake-ca"},
SerialNumber: big.NewInt(10),
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(time.Hour),
KeyUsage: x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
if err != nil {
return nil, fmt.Errorf("x509.CreateCertificate(%v) failed: %v", tmpl, err)
}
// The PEM encoding of the self-signed certificate is stored because we need
// to return a chain of certificates in the response, starting with the
// client certificate and ending in the root.
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, fmt.Errorf("x509.ParseCertificate(%v) failed: %v", certDER, err)
}
return &fakeCA{
withErrors: withErrors,
withShortLife: withShortLife,
ccChan: ccChan,
key: key,
cert: cert,
certPEM: certPEM,
}, nil
}
// CreateCertificate helps implement the MeshCA service.
//
// If the fakeMeshCA was created with `withErrors` set to true, the first
// `maxErrCount` number of RPC return errors. Subsequent requests are signed and
// returned without error.
func (f *fakeCA) CreateCertificate(ctx context.Context, req *meshpb.MeshCertificateRequest) (*meshpb.MeshCertificateResponse, error) {
if f.withErrors {
if f.errors < maxErrCount {
f.errors++
return nil, errors.New("fake Mesh CA error")
}
}
csrPEM := []byte(req.GetCsr())
block, _ := pem.Decode(csrPEM)
if block == nil {
return nil, fmt.Errorf("failed to decode received CSR: %v", csrPEM)
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse received CSR: %v", csrPEM)
}
// By default, we create certs which are valid for an hour. But if
// `withShortLife` is set, we create certs which are valid only for a couple
// of seconds.
now := time.Now()
notBefore, notAfter := now.Add(-defaultTestCertLife), now.Add(defaultTestCertLife)
if f.withShortLife {
notBefore, notAfter = now.Add(-shortTestCertLife), now.Add(shortTestCertLife)
}
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "signed-cert"},
SerialNumber: big.NewInt(10),
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, f.cert, csr.PublicKey, f.key)
if err != nil {
return nil, fmt.Errorf("x509.CreateCertificate(%v) failed: %v", tmpl, err)
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
// Push to ccChan to indicate that the RPC is processed.
f.ccChan.Send(nil)
certChain := []string{
string(certPEM), // Signed certificate corresponding to CSR
string(f.certPEM), // Root certificate
}
return &meshpb.MeshCertificateResponse{CertChain: certChain}, nil
}
// opts wraps the options to be passed to setup.
type opts struct {
// Whether the CA returns certs with short lifetime. Used to test client refresh.
withShortLife bool
// Whether the CA returns errors to begin with. Used to test client backoff.
withbackoff bool
}
// events wraps channels which indicate different events.
type events struct {
// Pushed to when the plugin dials the MeshCA.
dialDone *testutils.Channel
// Pushed to when CreateCertifcate() succeeds on the MeshCA.
createCertDone *testutils.Channel
// Pushed to when the plugin updates the distributor with new key material.
keyMaterialDone *testutils.Channel
// Pushed to when the client backs off after a failed CreateCertificate().
backoffDone *testutils.Channel
}
// setup performs tasks common to all tests in this file.
func setup(t *testing.T, o opts) (events, string, func()) {
t.Helper()
// Create a fake MeshCA which pushes events on the passed channel for
// successful RPCs.
createCertDone := testutils.NewChannel()
fs, err := newFakeMeshCA(createCertDone, o.withbackoff, o.withShortLife)
if err != nil {
t.Fatal(err)
}
// Create a gRPC server and register the fake MeshCA on it.
server := grpc.NewServer()
meshgrpc.RegisterMeshCertificateServiceServer(server, fs)
// Start a net.Listener on a local port, and pass it to the gRPC server
// created above and start serving.
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatal(err)
}
addr := lis.Addr().String()
go server.Serve(lis)
// Override the plugin's dial function and perform a blocking dial. Also
// push on dialDone once the dial is complete so that test can block on this
// event before verifying other things.
dialDone := testutils.NewChannel()
origDialFunc := grpcDialFunc
grpcDialFunc = func(uri string, _ ...grpc.DialOption) (*grpc.ClientConn, error) {
if uri != addr {
t.Fatalf("plugin dialing MeshCA at %s, want %s", uri, addr)
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
cc, err := grpc.DialContext(ctx, uri, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
t.Fatalf("grpc.DialContext(%s) failed: %v", addr, err)
}
dialDone.Send(nil)
return cc, nil
}
// Override the plugin's newDistributorFunc and return a wrappedDistributor
// which allows the test to be notified whenever the plugin pushes new key
// material into the distributor.
origDistributorFunc := newDistributorFunc
keyMaterialDone := testutils.NewChannel()
d := newWrappedDistributor(keyMaterialDone)
newDistributorFunc = func() distributor { return d }
// Override the plugin's backoff function to perform no real backoff, but
// push on a channel so that the test can verifiy that backoff actually
// happened.
backoffDone := testutils.NewChannelWithSize(maxErrCount)
origBackoffFunc := backoffFunc
if o.withbackoff {
// Override the plugin's backoff function with this, so that we can verify
// that a backoff actually was triggered.
backoffFunc = func(v int) time.Duration {
backoffDone.Send(v)
return 0
}
}
// Return all the channels, and a cancel function to undo all the overrides.
e := events{
dialDone: dialDone,
createCertDone: createCertDone,
keyMaterialDone: keyMaterialDone,
backoffDone: backoffDone,
}
done := func() {
server.Stop()
grpcDialFunc = origDialFunc
newDistributorFunc = origDistributorFunc
backoffFunc = origBackoffFunc
}
return e, addr, done
}
// wrappedDistributor wraps a distributor and pushes on a channel whenever new
// key material is pushed to the distributor.
type wrappedDistributor struct {
*certprovider.Distributor
kmChan *testutils.Channel
}
func newWrappedDistributor(kmChan *testutils.Channel) *wrappedDistributor {
return &wrappedDistributor{
kmChan: kmChan,
Distributor: certprovider.NewDistributor(),
}
}
func (wd *wrappedDistributor) Set(km *certprovider.KeyMaterial, err error) {
wd.Distributor.Set(km, err)
wd.kmChan.Send(nil)
}
// TestCreateCertificate verifies the simple case where the MeshCA server
// returns a good certificate.
func (s) TestCreateCertificate(t *testing.T) {
e, addr, cancel := setup(t, opts{})
defer cancel()
// Set the MeshCA targetURI to point to our fake MeshCA.
inputConfig := json.RawMessage(fmt.Sprintf(goodConfigFormatStr, addr))
// Lookup MeshCA plugin builder, parse config and start the plugin.
prov, err := certprovider.GetProvider(pluginName, inputConfig, certprovider.BuildOptions{})
if err != nil {
t.Fatalf("GetProvider(%s, %s) failed: %v", pluginName, string(inputConfig), err)
}
defer prov.Close()
// Wait till the plugin dials the MeshCA server.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.dialDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to dial MeshCA")
}
// Wait till the plugin makes a CreateCertificate() call.
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.createCertDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to make CreateCertificate RPC")
}
// We don't really care about the exact key material returned here. All we
// care about is whether we get any key material at all, and that we don't
// get any errors.
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err = prov.KeyMaterial(ctx); err != nil {
t.Fatalf("provider.KeyMaterial(ctx) failed: %v", err)
}
}
// TestCreateCertificateWithBackoff verifies the case where the MeshCA server
// returns errors initially and then returns a good certificate. The test makes
// sure that the client backs off when the server returns errors.
func (s) TestCreateCertificateWithBackoff(t *testing.T) {
e, addr, cancel := setup(t, opts{withbackoff: true})
defer cancel()
// Set the MeshCA targetURI to point to our fake MeshCA.
inputConfig := json.RawMessage(fmt.Sprintf(goodConfigFormatStr, addr))
// Lookup MeshCA plugin builder, parse config and start the plugin.
prov, err := certprovider.GetProvider(pluginName, inputConfig, certprovider.BuildOptions{})
if err != nil {
t.Fatalf("GetProvider(%s, %s) failed: %v", pluginName, string(inputConfig), err)
}
defer prov.Close()
// Wait till the plugin dials the MeshCA server.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.dialDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to dial MeshCA")
}
// Making the CreateCertificateRPC involves generating the keys, creating
// the CSR etc which seem to take reasonable amount of time. And in this
// test, the first two attempts will fail. Hence we give it a reasonable
// deadline here.
ctx, cancel = context.WithTimeout(context.Background(), 3*defaultTestTimeout)
defer cancel()
if _, err := e.createCertDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to make CreateCertificate RPC")
}
// The first `maxErrCount` calls to CreateCertificate end in failure, and
// should lead to a backoff.
for i := 0; i < maxErrCount; i++ {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.backoffDone.Receive(ctx); err != nil {
t.Fatalf("plugin failed to backoff after error from fake server: %v", err)
}
}
// We don't really care about the exact key material returned here. All we
// care about is whether we get any key material at all, and that we don't
// get any errors.
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err = prov.KeyMaterial(ctx); err != nil {
t.Fatalf("provider.KeyMaterial(ctx) failed: %v", err)
}
}
// TestCreateCertificateWithRefresh verifies the case where the MeshCA returns a
// certificate with a really short lifetime, and makes sure that the plugin
// refreshes the cert in time.
func (s) TestCreateCertificateWithRefresh(t *testing.T) {
e, addr, cancel := setup(t, opts{withShortLife: true})
defer cancel()
// Set the MeshCA targetURI to point to our fake MeshCA.
inputConfig := json.RawMessage(fmt.Sprintf(goodConfigFormatStr, addr))
// Lookup MeshCA plugin builder, parse config and start the plugin.
prov, err := certprovider.GetProvider(pluginName, inputConfig, certprovider.BuildOptions{})
if err != nil {
t.Fatalf("GetProvider(%s, %s) failed: %v", pluginName, string(inputConfig), err)
}
defer prov.Close()
// Wait till the plugin dials the MeshCA server.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.dialDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to dial MeshCA")
}
// Wait till the plugin makes a CreateCertificate() call.
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := e.createCertDone.Receive(ctx); err != nil {
t.Fatal("timeout waiting for plugin to make CreateCertificate RPC")
}
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
km1, err := prov.KeyMaterial(ctx)
if err != nil {
t.Fatalf("provider.KeyMaterial(ctx) failed: %v", err)
}
// At this point, we have read the first key material, and since the
// returned key material has a really short validity period, we expect the
// key material to be refreshed quite soon. We drain the channel on which
// the event corresponding to setting of new key material is pushed. This
// enables us to block on the same channel, waiting for refreshed key
// material.
// Since we do not expect this call to block, it is OK to pass the
// background context.
e.keyMaterialDone.Receive(context.Background())
// Wait for the next call to CreateCertificate() to refresh the certificate
// returned earlier.
ctx, cancel = context.WithTimeout(context.Background(), 2*shortTestCertLife)
defer cancel()
if _, err := e.keyMaterialDone.Receive(ctx); err != nil {
t.Fatalf("CreateCertificate() RPC not made: %v", err)
}
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
km2, err := prov.KeyMaterial(ctx)
if err != nil {
t.Fatalf("provider.KeyMaterial(ctx) failed: %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(km1, km2) {
t.Error("certificate refresh did not happen in the background")
}
}

Просмотреть файл

@ -48,11 +48,6 @@ mkdir -p ${WORKDIR}/googleapis/google/rpc
echo "curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/rpc/code.proto"
curl --silent https://raw.githubusercontent.com/googleapis/googleapis/master/google/rpc/code.proto > ${WORKDIR}/googleapis/google/rpc/code.proto
# Pull in the MeshCA service proto.
mkdir -p ${WORKDIR}/istio/istio/google/security/meshca/v1
echo "curl https://raw.githubusercontent.com/istio/istio/master/security/proto/providers/google/meshca.proto"
curl --silent https://raw.githubusercontent.com/istio/istio/master/security/proto/providers/google/meshca.proto > ${WORKDIR}/istio/istio/google/security/meshca/v1/meshca.proto
mkdir -p ${WORKDIR}/out
# Generates sources without the embed requirement
@ -76,7 +71,6 @@ SOURCES=(
${WORKDIR}/grpc-proto/grpc/service_config/service_config.proto
${WORKDIR}/grpc-proto/grpc/testing/*.proto
${WORKDIR}/grpc-proto/grpc/core/*.proto
${WORKDIR}/istio/istio/google/security/meshca/v1/meshca.proto
)
# These options of the form 'Mfoo.proto=bar' instruct the codegen to use an
@ -122,8 +116,4 @@ mv ${WORKDIR}/out/grpc/service_config/service_config.pb.go internal/proto/grpc_s
mv ${WORKDIR}/out/grpc/testing/*.pb.go interop/grpc_testing/
mv ${WORKDIR}/out/grpc/core/*.pb.go interop/grpc_testing/core/
# istio/google/security/meshca/v1/meshca.proto does not have a go_package option.
mkdir -p ${WORKDIR}/out/google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1/
mv ${WORKDIR}/out/istio/google/security/meshca/v1/* ${WORKDIR}/out/google.golang.org/grpc/credentials/tls/certprovider/meshca/internal/v1/
cp -R ${WORKDIR}/out/google.golang.org/grpc/* .

Просмотреть файл

@ -1,25 +0,0 @@
// +build go1.13
/*
*
* 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 xds
import (
_ "google.golang.org/grpc/credentials/tls/certprovider/meshca" // Register the MeshCA certificate provider plugin.
)