1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17// Package envelope transforms values for storage at rest using a Envelope provider 18package envelope 19 20import ( 21 "context" 22 "fmt" 23 "net" 24 "net/url" 25 "strings" 26 "sync" 27 "time" 28 29 "k8s.io/klog/v2" 30 31 "google.golang.org/grpc" 32 33 kmsapi "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1" 34) 35 36const ( 37 // Now only supported unix domain socket. 38 unixProtocol = "unix" 39 40 // Current version for the protocol interface definition. 41 kmsapiVersion = "v1beta1" 42 43 versionErrorf = "KMS provider api version %s is not supported, only %s is supported now" 44) 45 46// The gRPC implementation for envelope.Service. 47type gRPCService struct { 48 kmsClient kmsapi.KeyManagementServiceClient 49 connection *grpc.ClientConn 50 callTimeout time.Duration 51 mux sync.RWMutex 52 versionChecked bool 53} 54 55// NewGRPCService returns an envelope.Service which use gRPC to communicate the remote KMS provider. 56func NewGRPCService(endpoint string, callTimeout time.Duration) (Service, error) { 57 klog.V(4).Infof("Configure KMS provider with endpoint: %s", endpoint) 58 59 addr, err := parseEndpoint(endpoint) 60 if err != nil { 61 return nil, err 62 } 63 64 s := &gRPCService{callTimeout: callTimeout} 65 s.connection, err = grpc.Dial( 66 addr, 67 grpc.WithInsecure(), 68 grpc.WithUnaryInterceptor(s.interceptor), 69 grpc.WithDefaultCallOptions(grpc.WaitForReady(true)), 70 grpc.WithContextDialer( 71 func(context.Context, string) (net.Conn, error) { 72 // Ignoring addr and timeout arguments: 73 // addr - comes from the closure 74 c, err := net.DialUnix(unixProtocol, nil, &net.UnixAddr{Name: addr}) 75 if err != nil { 76 klog.Errorf("failed to create connection to unix socket: %s, error: %v", addr, err) 77 } else { 78 klog.V(4).Infof("Successfully dialed Unix socket %v", addr) 79 } 80 return c, err 81 })) 82 83 if err != nil { 84 return nil, fmt.Errorf("failed to create connection to %s, error: %v", endpoint, err) 85 } 86 87 s.kmsClient = kmsapi.NewKeyManagementServiceClient(s.connection) 88 return s, nil 89} 90 91// Parse the endpoint to extract schema, host or path. 92func parseEndpoint(endpoint string) (string, error) { 93 if len(endpoint) == 0 { 94 return "", fmt.Errorf("remote KMS provider can't use empty string as endpoint") 95 } 96 97 u, err := url.Parse(endpoint) 98 if err != nil { 99 return "", fmt.Errorf("invalid endpoint %q for remote KMS provider, error: %v", endpoint, err) 100 } 101 102 if u.Scheme != unixProtocol { 103 return "", fmt.Errorf("unsupported scheme %q for remote KMS provider", u.Scheme) 104 } 105 106 // Linux abstract namespace socket - no physical file required 107 // Warning: Linux Abstract sockets have not concept of ACL (unlike traditional file based sockets). 108 // However, Linux Abstract sockets are subject to Linux networking namespace, so will only be accessible to 109 // containers within the same pod (unless host networking is used). 110 if strings.HasPrefix(u.Path, "/@") { 111 return strings.TrimPrefix(u.Path, "/"), nil 112 } 113 114 return u.Path, nil 115} 116 117func (g *gRPCService) checkAPIVersion(ctx context.Context) error { 118 g.mux.Lock() 119 defer g.mux.Unlock() 120 121 if g.versionChecked { 122 return nil 123 } 124 125 request := &kmsapi.VersionRequest{Version: kmsapiVersion} 126 response, err := g.kmsClient.Version(ctx, request) 127 if err != nil { 128 return fmt.Errorf("failed get version from remote KMS provider: %v", err) 129 } 130 if response.Version != kmsapiVersion { 131 return fmt.Errorf(versionErrorf, response.Version, kmsapiVersion) 132 } 133 g.versionChecked = true 134 135 klog.V(4).Infof("Version of KMS provider is %s", response.Version) 136 return nil 137} 138 139// Decrypt a given data string to obtain the original byte data. 140func (g *gRPCService) Decrypt(cipher []byte) ([]byte, error) { 141 ctx, cancel := context.WithTimeout(context.Background(), g.callTimeout) 142 defer cancel() 143 144 request := &kmsapi.DecryptRequest{Cipher: cipher, Version: kmsapiVersion} 145 response, err := g.kmsClient.Decrypt(ctx, request) 146 if err != nil { 147 return nil, err 148 } 149 return response.Plain, nil 150} 151 152// Encrypt bytes to a string ciphertext. 153func (g *gRPCService) Encrypt(plain []byte) ([]byte, error) { 154 ctx, cancel := context.WithTimeout(context.Background(), g.callTimeout) 155 defer cancel() 156 157 request := &kmsapi.EncryptRequest{Plain: plain, Version: kmsapiVersion} 158 response, err := g.kmsClient.Encrypt(ctx, request) 159 if err != nil { 160 return nil, err 161 } 162 return response.Cipher, nil 163} 164 165func (g *gRPCService) interceptor( 166 ctx context.Context, 167 method string, 168 req interface{}, 169 reply interface{}, 170 cc *grpc.ClientConn, 171 invoker grpc.UnaryInvoker, 172 opts ...grpc.CallOption, 173) error { 174 if !kmsapi.IsVersionCheckMethod(method) { 175 if err := g.checkAPIVersion(ctx); err != nil { 176 return err 177 } 178 } 179 180 return invoker(ctx, method, req, reply, cc, opts...) 181} 182