k8s.io/apiserver@v0.31.1/pkg/storage/value/encrypt/envelope/grpc_service.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package envelope transforms values for storage at rest using a Envelope provider
    18  package envelope
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net"
    24  	"sync"
    25  	"time"
    26  
    27  	"google.golang.org/grpc"
    28  	"google.golang.org/grpc/credentials/insecure"
    29  
    30  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    31  	"k8s.io/klog/v2"
    32  	kmsapi "k8s.io/kms/apis/v1beta1"
    33  	"k8s.io/kms/pkg/util"
    34  )
    35  
    36  const (
    37  	// unixProtocol is the only supported protocol for remote KMS provider.
    38  	unixProtocol = "unix"
    39  	// Current version for the protocol interface definition.
    40  	kmsapiVersion = "v1beta1"
    41  
    42  	versionErrorf = "KMS provider api version %s is not supported, only %s is supported now"
    43  )
    44  
    45  // The gRPC implementation for envelope.Service.
    46  type gRPCService struct {
    47  	kmsClient      kmsapi.KeyManagementServiceClient
    48  	connection     *grpc.ClientConn
    49  	callTimeout    time.Duration
    50  	mux            sync.RWMutex
    51  	versionChecked bool
    52  }
    53  
    54  // NewGRPCService returns an envelope.Service which use gRPC to communicate the remote KMS provider.
    55  func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Duration) (Service, error) {
    56  	klog.V(4).InfoS("Configure KMS provider", "endpoint", endpoint)
    57  
    58  	addr, err := util.ParseEndpoint(endpoint)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	s := &gRPCService{callTimeout: callTimeout}
    64  	s.connection, err = grpc.Dial(
    65  		addr,
    66  		grpc.WithTransportCredentials(insecure.NewCredentials()),
    67  		grpc.WithUnaryInterceptor(s.interceptor),
    68  		grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
    69  		grpc.WithContextDialer(
    70  			func(context.Context, string) (net.Conn, error) {
    71  				// Ignoring addr and timeout arguments:
    72  				// addr - comes from the closure
    73  				c, err := net.DialUnix(unixProtocol, nil, &net.UnixAddr{Name: addr})
    74  				if err != nil {
    75  					klog.ErrorS(err, "failed to create connection to unix socket", "addr", addr)
    76  				} else {
    77  					klog.V(4).InfoS("Successfully dialed Unix socket", "addr", addr)
    78  				}
    79  				return c, err
    80  			}))
    81  
    82  	if err != nil {
    83  		return nil, fmt.Errorf("failed to create connection to %s, error: %v", endpoint, err)
    84  	}
    85  
    86  	s.kmsClient = kmsapi.NewKeyManagementServiceClient(s.connection)
    87  
    88  	go func() {
    89  		defer utilruntime.HandleCrash()
    90  
    91  		<-ctx.Done()
    92  		_ = s.connection.Close()
    93  	}()
    94  
    95  	return s, nil
    96  }
    97  
    98  func (g *gRPCService) checkAPIVersion(ctx context.Context) error {
    99  	g.mux.Lock()
   100  	defer g.mux.Unlock()
   101  
   102  	if g.versionChecked {
   103  		return nil
   104  	}
   105  
   106  	request := &kmsapi.VersionRequest{Version: kmsapiVersion}
   107  	response, err := g.kmsClient.Version(ctx, request)
   108  	if err != nil {
   109  		return fmt.Errorf("failed get version from remote KMS provider: %v", err)
   110  	}
   111  	if response.Version != kmsapiVersion {
   112  		return fmt.Errorf(versionErrorf, response.Version, kmsapiVersion)
   113  	}
   114  	g.versionChecked = true
   115  
   116  	klog.V(4).InfoS("KMS provider api version verified", "version", response.Version)
   117  	return nil
   118  }
   119  
   120  // Decrypt a given data string to obtain the original byte data.
   121  func (g *gRPCService) Decrypt(cipher []byte) ([]byte, error) {
   122  	ctx, cancel := context.WithTimeout(context.Background(), g.callTimeout)
   123  	defer cancel()
   124  
   125  	request := &kmsapi.DecryptRequest{Cipher: cipher, Version: kmsapiVersion}
   126  	response, err := g.kmsClient.Decrypt(ctx, request)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return response.Plain, nil
   131  }
   132  
   133  // Encrypt bytes to a string ciphertext.
   134  func (g *gRPCService) Encrypt(plain []byte) ([]byte, error) {
   135  	ctx, cancel := context.WithTimeout(context.Background(), g.callTimeout)
   136  	defer cancel()
   137  
   138  	request := &kmsapi.EncryptRequest{Plain: plain, Version: kmsapiVersion}
   139  	response, err := g.kmsClient.Encrypt(ctx, request)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	return response.Cipher, nil
   144  }
   145  
   146  func (g *gRPCService) interceptor(
   147  	ctx context.Context,
   148  	method string,
   149  	req interface{},
   150  	reply interface{},
   151  	cc *grpc.ClientConn,
   152  	invoker grpc.UnaryInvoker,
   153  	opts ...grpc.CallOption,
   154  ) error {
   155  	if !kmsapi.IsVersionCheckMethod(method) {
   156  		if err := g.checkAPIVersion(ctx); err != nil {
   157  			return err
   158  		}
   159  	}
   160  
   161  	return invoker(ctx, method, req, reply, cc, opts...)
   162  }