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 }