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

     1  //go:build !windows
     2  // +build !windows
     3  
     4  /*
     5  Copyright 2022 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package v2
    21  
    22  import (
    23  	"context"
    24  	"encoding/base64"
    25  	"fmt"
    26  	"net"
    27  	"os"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"google.golang.org/grpc"
    33  	"google.golang.org/grpc/codes"
    34  	"google.golang.org/grpc/status"
    35  
    36  	"k8s.io/apimachinery/pkg/util/wait"
    37  	"k8s.io/klog/v2"
    38  	kmsapi "k8s.io/kms/apis/v2"
    39  )
    40  
    41  const (
    42  	// Now only supported unix domain socket.
    43  	unixProtocol = "unix"
    44  
    45  	// Current version for the protocol interface definition.
    46  	kmsapiVersion = "v2beta1"
    47  )
    48  
    49  // Base64Plugin gRPC sever for a mock KMS provider.
    50  // Uses base64 to simulate encrypt and decrypt.
    51  type Base64Plugin struct {
    52  	grpcServer         *grpc.Server
    53  	listener           net.Listener
    54  	mu                 *sync.Mutex
    55  	lastEncryptRequest *kmsapi.EncryptRequest
    56  	inFailedState      bool
    57  	ver                string
    58  	socketPath         string
    59  	keyID              string
    60  }
    61  
    62  // NewBase64Plugin is a constructor for Base64Plugin.
    63  func NewBase64Plugin(t testing.TB, socketPath string) *Base64Plugin {
    64  	server := grpc.NewServer()
    65  	result := &Base64Plugin{
    66  		grpcServer: server,
    67  		mu:         &sync.Mutex{},
    68  		ver:        kmsapiVersion,
    69  		socketPath: socketPath,
    70  		keyID:      "1",
    71  	}
    72  
    73  	kmsapi.RegisterKeyManagementServiceServer(server, result)
    74  
    75  	if err := result.start(); err != nil {
    76  		t.Fatalf("failed to start KMS plugin, err: %v", err)
    77  	}
    78  	t.Cleanup(result.CleanUp)
    79  	if err := waitForBase64PluginToBeUp(result); err != nil {
    80  		t.Fatalf("failed to start KMS plugin: err: %v", err)
    81  	}
    82  	return result
    83  }
    84  
    85  // waitForBase64PluginToBeUp waits until the plugin is ready to serve requests.
    86  func waitForBase64PluginToBeUp(plugin *Base64Plugin) error {
    87  	var gRPCErr error
    88  	var resp *kmsapi.StatusResponse
    89  	pollErr := wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) {
    90  		resp, gRPCErr = plugin.Status(context.Background(), &kmsapi.StatusRequest{})
    91  		return gRPCErr == nil && resp.Healthz == "ok", nil
    92  	})
    93  
    94  	if pollErr != nil {
    95  		return fmt.Errorf("failed to start kms-plugin, gRPC error: %v, poll error: %v", gRPCErr, pollErr)
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // waitForBase64PluginToBeUpdated waits until the plugin updates keyID.
   102  func WaitForBase64PluginToBeUpdated(plugin *Base64Plugin) error {
   103  	var gRPCErr error
   104  	var resp *kmsapi.StatusResponse
   105  
   106  	updatePollErr := wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) {
   107  		resp, gRPCErr = plugin.Status(context.Background(), &kmsapi.StatusRequest{})
   108  		klog.InfoS("WaitForBase64PluginToBeUpdated", "keyID", resp.KeyId)
   109  		return gRPCErr == nil && resp.Healthz == "ok" && resp.KeyId == "2", nil
   110  	})
   111  
   112  	if updatePollErr != nil {
   113  		return fmt.Errorf("failed to update keyID for kmsv2-plugin, gRPC error: %w, updatePoll error: %w", gRPCErr, updatePollErr)
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // LastEncryptRequest returns the last EncryptRequest.Plain sent to the plugin.
   120  func (s *Base64Plugin) LastEncryptRequest() []byte {
   121  	return s.lastEncryptRequest.Plaintext
   122  }
   123  
   124  // SetVersion sets the version of kms-plugin.
   125  func (s *Base64Plugin) SetVersion(ver string) {
   126  	s.ver = ver
   127  }
   128  
   129  // start starts plugin's gRPC service.
   130  func (s *Base64Plugin) start() error {
   131  	var err error
   132  	s.listener, err = net.Listen(unixProtocol, s.socketPath)
   133  	if err != nil {
   134  		return fmt.Errorf("failed to listen on the unix socket, error: %v", err)
   135  	}
   136  	klog.InfoS("Starting KMS Plugin", "socketPath", s.socketPath)
   137  
   138  	go s.grpcServer.Serve(s.listener)
   139  	return nil
   140  }
   141  
   142  // CleanUp stops gRPC server and the underlying listener.
   143  func (s *Base64Plugin) CleanUp() {
   144  	s.grpcServer.Stop()
   145  	_ = s.listener.Close()
   146  	_ = os.Remove(s.socketPath)
   147  }
   148  
   149  // EnterFailedState places the plugin into failed state.
   150  func (s *Base64Plugin) EnterFailedState() {
   151  	s.mu.Lock()
   152  	defer s.mu.Unlock()
   153  	s.inFailedState = true
   154  }
   155  
   156  // ExitFailedState removes the plugin from the failed state.
   157  func (s *Base64Plugin) ExitFailedState() {
   158  	s.mu.Lock()
   159  	defer s.mu.Unlock()
   160  	s.inFailedState = false
   161  }
   162  
   163  // Update keyID for the plugin.
   164  func (s *Base64Plugin) UpdateKeyID() {
   165  	s.mu.Lock()
   166  	defer s.mu.Unlock()
   167  	s.keyID = "2"
   168  }
   169  
   170  // Status returns the status of the kms-plugin.
   171  func (s *Base64Plugin) Status(ctx context.Context, request *kmsapi.StatusRequest) (*kmsapi.StatusResponse, error) {
   172  	klog.V(3).InfoS("Received request for Status", "request", request)
   173  	s.mu.Lock()
   174  	defer s.mu.Unlock()
   175  
   176  	if s.inFailedState {
   177  		return nil, status.Error(codes.FailedPrecondition, "failed precondition - key disabled")
   178  	}
   179  
   180  	return &kmsapi.StatusResponse{Version: s.ver, Healthz: "ok", KeyId: s.keyID}, nil
   181  }
   182  
   183  // Decrypt performs base64 decoding of the payload of kms.DecryptRequest.
   184  func (s *Base64Plugin) Decrypt(ctx context.Context, request *kmsapi.DecryptRequest) (*kmsapi.DecryptResponse, error) {
   185  	klog.V(3).InfoS("Received Decrypt Request", "ciphertext", string(request.Ciphertext))
   186  
   187  	s.mu.Lock()
   188  	defer s.mu.Unlock()
   189  	if s.inFailedState {
   190  		return nil, status.Error(codes.FailedPrecondition, "failed precondition - key disabled")
   191  	}
   192  	if len(request.Uid) == 0 {
   193  		return nil, status.Error(codes.InvalidArgument, "uid is required")
   194  	}
   195  
   196  	buf := make([]byte, base64.StdEncoding.DecodedLen(len(request.Ciphertext)))
   197  	n, err := base64.StdEncoding.Decode(buf, request.Ciphertext)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return &kmsapi.DecryptResponse{Plaintext: buf[:n]}, nil
   203  }
   204  
   205  // Encrypt performs base64 encoding of the payload of kms.EncryptRequest.
   206  func (s *Base64Plugin) Encrypt(ctx context.Context, request *kmsapi.EncryptRequest) (*kmsapi.EncryptResponse, error) {
   207  	klog.V(3).InfoS("Received Encrypt Request", "plaintext", string(request.Plaintext))
   208  	s.mu.Lock()
   209  	defer s.mu.Unlock()
   210  	s.lastEncryptRequest = request
   211  
   212  	if s.inFailedState {
   213  		return nil, status.Error(codes.FailedPrecondition, "failed precondition - key disabled")
   214  	}
   215  	if len(request.Uid) == 0 {
   216  		return nil, status.Error(codes.InvalidArgument, "uid is required")
   217  	}
   218  
   219  	buf := make([]byte, base64.StdEncoding.EncodedLen(len(request.Plaintext)))
   220  	base64.StdEncoding.Encode(buf, request.Plaintext)
   221  
   222  	return &kmsapi.EncryptResponse{Ciphertext: buf, KeyId: s.keyID, Annotations: map[string][]byte{"local-kek.kms.kubernetes.io": []byte("encrypted-local-kek")}}, nil
   223  }