meshca: remove meshca certificate provider implementation (#4385)
This commit is contained in:
Родитель
ebd6aba675
Коммит
75497df97f
|
@ -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/* .
|
||||
|
|
25
xds/go113.go
25
xds/go113.go
|
@ -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.
|
||||
)
|
Загрузка…
Ссылка в новой задаче