diff --git a/config/keyprovider-config/config.go b/config/keyprovider-config/config.go index 4785a83..f4605d5 100644 --- a/config/keyprovider-config/config.go +++ b/config/keyprovider-config/config.go @@ -29,10 +29,39 @@ type Command struct { Args []string `json:"args,omitempty"` } +// GrpcTLS describes the structure of TLS configuration for gRPC connection, it consist of CA certificate, +// client certificate and client key +type GrpcTLS struct { + // RootCAFile defines path to the PEM file with the set of root certificate authorities + // that clients use when verifying server certificates. + // If RootCAs is nil, TLS uses the host's root CA set. + RootCAFile string `json:"root-ca-file,omitempty"` + + // CertFile contains the path to the x509 PEM encoded client certificate. + CertFile string `json:"cert-file,omitempty"` + // KeyFile contains the path to the PEM encoded client key. + KeyFile string `json:"key-file,omitempty"` + + // ServerName is used to verify the hostname on the returned + // certificates unless InsecureSkipVerify is given. It is also included + // in the client's handshake to support virtual hosting unless it is + // an IP address. + ServerName string `json:"server-name,omitempty"` + + // InsecureSkipVerify controls whether a client verifies the + // server's certificate chain and host name. + // If InsecureSkipVerify is true, TLS accepts any certificate + // presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to man-in-the-middle attacks. + // This should be used only for testing. + InsecureSkipVerify bool `json:"insecure-skip-verify,omitempty"` +} + // KeyProviderAttrs describes the structure of key provider, it defines the way of invocation to key provider type KeyProviderAttrs struct { Command *Command `json:"cmd,omitempty"` Grpc string `json:"grpc,omitempty"` + GrpcTLS *GrpcTLS `json:"grpc-tls,omitempty"` } // OcicryptConfig represents the format of an ocicrypt_provider.conf config file diff --git a/keywrap/keyprovider/keyprovider.go b/keywrap/keyprovider/keyprovider.go index 6ac0fcb..bccbff9 100644 --- a/keywrap/keyprovider/keyprovider.go +++ b/keywrap/keyprovider/keyprovider.go @@ -18,9 +18,12 @@ package keyprovider import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" + "os" "github.com/containers/ocicrypt/config" keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config" @@ -29,6 +32,7 @@ import ( keyproviderpb "github.com/containers/ocicrypt/utils/keyprovider" log "github.com/sirupsen/logrus" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) type keyProviderKeyWrapper struct { @@ -118,7 +122,7 @@ func (kw *keyProviderKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []b } return protocolOuput.KeyWrapResults.Annotation, nil } else if kw.attrs.Grpc != "" { - protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, OpKeyWrap) + protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, kw.attrs.GrpcTLS, OpKeyWrap) if err != nil { return nil, fmt.Errorf("error while retrieving keyprovider protocol grpc output: %w", err) } @@ -154,7 +158,7 @@ func (kw *keyProviderKeyWrapper) UnwrapKey(dc *config.DecryptConfig, jsonString return protocolOuput.KeyUnwrapResults.OptsData, nil } else if kw.attrs.Grpc != "" { - protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, OpKeyUnwrap) + protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, kw.attrs.GrpcTLS, OpKeyUnwrap) if err != nil { // If err is not nil, then ignore it and continue with rest of the given keyproviders return nil, err @@ -165,12 +169,55 @@ func (kw *keyProviderKeyWrapper) UnwrapKey(dc *config.DecryptConfig, jsonString return nil, errors.New("Unsupported keyprovider invocation. Supported invocation methods are grpc and cmd") } -func getProviderGRPCOutput(input []byte, connString string, operation KeyProviderKeyWrapProtocolOperation) (*KeyProviderKeyWrapProtocolOutput, error) { +func getProviderGRPCOutput(input []byte, connString string, grpcTls *keyproviderconfig.GrpcTLS, operation KeyProviderKeyWrapProtocolOperation) (*KeyProviderKeyWrapProtocolOutput, error) { var protocolOuput KeyProviderKeyWrapProtocolOutput var grpcOutput *keyproviderpb.KeyProviderKeyWrapProtocolOutput - cc, err := grpc.Dial(connString, grpc.WithInsecure()) - if err != nil { - return nil, fmt.Errorf("error while dialing rpc server: %w", err) + + var cc *grpc.ClientConn + var err error + + if grpcTls != nil { + var rootCAs *x509.CertPool + if grpcTls.RootCAFile != "" { + pem, err := os.ReadFile(grpcTls.RootCAFile) + if err != nil { + return nil, fmt.Errorf("failed to load root CA certificates error=%v", err) + } + if !rootCAs.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("no root CA certs parsed from file ") + } + } else { + rootCAs, err = x509.SystemCertPool() + if err != nil { + return nil, fmt.Errorf("error reading SystemCertPool error=%v", err) + } + } + + var clientCerts []tls.Certificate + if grpcTls.CertFile != "" && grpcTls.KeyFile != "" { + cert, err := tls.LoadX509KeyPair(grpcTls.CertFile, grpcTls.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load client certificate and key: %v", err) + } + clientCerts = []tls.Certificate{cert} + } + + tlsConfig := &tls.Config{ + RootCAs: rootCAs, + ServerName: grpcTls.ServerName, + InsecureSkipVerify: grpcTls.InsecureSkipVerify, + Certificates: clientCerts, + } + creds := credentials.NewTLS(tlsConfig) + cc, err = grpc.Dial(connString, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("error while dialing TLS rpc server: %w", err) + } + } else { + cc, err = grpc.Dial(connString, grpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("error while dialing rpc server: %w", err) + } } defer func() { derr := cc.Close() diff --git a/keywrap/keyprovider/keyprovider_test.go b/keywrap/keyprovider/keyprovider_test.go index 8d66729..19625a3 100644 --- a/keywrap/keyprovider/keyprovider_test.go +++ b/keywrap/keyprovider/keyprovider_test.go @@ -338,8 +338,15 @@ func TestKeyWrapKeyProviderGRPCSuccess(t *testing.T) { "path": "/usr/lib/keyprovider-2-unwrapkey", "args": [] } + }, + "keyprovider-4": { + "grpc": "localhost:3990", + "grpc-tls": { + "server-name": "localhost", + "insecure-skip-verify": false, + "root-ca-file": "/etc/ssl/certs/ca-certificates.crt" + } } - }} ` tempFile, _ := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)