github.com/cornelk/go-cloud@v0.17.1/secrets/gcpkms/kms.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package gcpkms provides a secrets implementation backed by Google Cloud KMS. 16 // Use OpenKeeper to construct a *secrets.Keeper. 17 // 18 // URLs 19 // 20 // For secrets.OpenKeeper, gcpkms registers for the scheme "gcpkms". 21 // The default URL opener will create a connection using use default 22 // credentials from the environment, as described in 23 // https://cloud.google.com/docs/authentication/production. 24 // To customize the URL opener, or for more details on the URL format, 25 // see URLOpener. 26 // See https://github.com/cornelk/go-cloud/concepts/urls/ for background information. 27 // 28 // As 29 // 30 // gcpkms exposes the following type for As: 31 // - Error: *google.golang.org/grpc/status.Status 32 package gcpkms // import "github.com/cornelk/go-cloud/secrets/gcpkms" 33 34 import ( 35 "context" 36 "fmt" 37 "net/url" 38 "path" 39 "sync" 40 41 cloudkms "cloud.google.com/go/kms/apiv1" 42 "github.com/cornelk/go-cloud/gcerrors" 43 "github.com/cornelk/go-cloud/gcp" 44 "github.com/cornelk/go-cloud/internal/gcerr" 45 "github.com/cornelk/go-cloud/internal/useragent" 46 "github.com/cornelk/go-cloud/secrets" 47 "github.com/google/wire" 48 "google.golang.org/api/option" 49 kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" 50 "google.golang.org/grpc/status" 51 ) 52 53 // endPoint is the address to access Google Cloud KMS API. 54 const endPoint = "cloudkms.googleapis.com:443" 55 56 // Dial returns a client to use with Cloud KMS and a clean-up function to close 57 // the client after used. 58 func Dial(ctx context.Context, ts gcp.TokenSource) (*cloudkms.KeyManagementClient, func(), error) { 59 c, err := cloudkms.NewKeyManagementClient(ctx, option.WithTokenSource(ts), useragent.ClientOption("secrets")) 60 return c, func() { c.Close() }, err 61 } 62 63 func init() { 64 secrets.DefaultURLMux().RegisterKeeper(Scheme, new(lazyCredsOpener)) 65 } 66 67 // Set holds Wire providers for this package. 68 var Set = wire.NewSet( 69 Dial, 70 wire.Struct(new(URLOpener), "Client"), 71 ) 72 73 // lazyCredsOpener obtains Application Default Credentials on the first call 74 // lazyCredsOpener obtains Application Default Credentials on the first call 75 // to OpenKeeperURL. 76 type lazyCredsOpener struct { 77 init sync.Once 78 opener *URLOpener 79 err error 80 } 81 82 func (o *lazyCredsOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) { 83 o.init.Do(func() { 84 creds, err := gcp.DefaultCredentials(ctx) 85 if err != nil { 86 o.err = err 87 return 88 } 89 client, _, err := Dial(ctx, creds.TokenSource) 90 if err != nil { 91 o.err = err 92 return 93 } 94 o.opener = &URLOpener{Client: client} 95 }) 96 if o.err != nil { 97 return nil, fmt.Errorf("open keeper %v: %v", u, o.err) 98 } 99 return o.opener.OpenKeeperURL(ctx, u) 100 } 101 102 // Scheme is the URL scheme gcpkms registers its URLOpener under on secrets.DefaultMux. 103 const Scheme = "gcpkms" 104 105 // URLOpener opens GCP KMS URLs like 106 // "gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]". 107 // 108 // The URL host+path are used as the key resource ID; see 109 // https://cloud.google.com/kms/docs/object-hierarchy#key for more details. 110 // 111 // No query parameters are supported. 112 type URLOpener struct { 113 // Client must be non-nil and be authenticated with "cloudkms" scope or equivalent. 114 Client *cloudkms.KeyManagementClient 115 116 // Options specifies the default options to pass to OpenKeeper. 117 Options KeeperOptions 118 } 119 120 // OpenKeeperURL opens the GCP KMS URLs. 121 func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) { 122 for param := range u.Query() { 123 return nil, fmt.Errorf("open keeper %v: invalid query parameter %q", u, param) 124 } 125 return OpenKeeper(o.Client, path.Join(u.Host, u.Path), &o.Options), nil 126 } 127 128 // OpenKeeper returns a *secrets.Keeper that uses Google Cloud KMS. 129 // You can use KeyResourceID to construct keyResourceID from its parts, 130 // or provide the whole string if you have it (e.g., from the GCP console). 131 // See https://cloud.google.com/kms/docs/object-hierarchy#key for more details. 132 // See the package documentation for an example. 133 func OpenKeeper(client *cloudkms.KeyManagementClient, keyResourceID string, opts *KeeperOptions) *secrets.Keeper { 134 return secrets.NewKeeper(&keeper{ 135 keyResourceID: keyResourceID, 136 client: client, 137 }) 138 } 139 140 // KeyResourceID constructs a key resourceID for GCP KMS. 141 // See https://cloud.google.com/kms/docs/object-hierarchy#key for more details. 142 func KeyResourceID(projectID, location, keyRing, key string) string { 143 return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", 144 projectID, location, keyRing, key) 145 } 146 147 // keeper implements driver.Keeper. 148 type keeper struct { 149 keyResourceID string 150 client *cloudkms.KeyManagementClient 151 } 152 153 // Decrypt decrypts the ciphertext using the key constructed from ki. 154 func (k *keeper) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) { 155 req := &kmspb.DecryptRequest{ 156 Name: k.keyResourceID, 157 Ciphertext: ciphertext, 158 } 159 resp, err := k.client.Decrypt(ctx, req) 160 if err != nil { 161 return nil, err 162 } 163 return resp.GetPlaintext(), nil 164 } 165 166 // Encrypt encrypts the plaintext into a ciphertext. 167 func (k *keeper) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) { 168 req := &kmspb.EncryptRequest{ 169 Name: k.keyResourceID, 170 Plaintext: plaintext, 171 } 172 resp, err := k.client.Encrypt(ctx, req) 173 if err != nil { 174 return nil, err 175 } 176 return resp.GetCiphertext(), nil 177 } 178 179 // Close implements driver.Keeper.Close. 180 func (k *keeper) Close() error { return nil } 181 182 // ErrorAs implements driver.Keeper.ErrorAs. 183 func (k *keeper) ErrorAs(err error, i interface{}) bool { 184 s, ok := status.FromError(err) 185 if !ok { 186 return false 187 } 188 p, ok := i.(**status.Status) 189 if !ok { 190 return false 191 } 192 *p = s 193 return true 194 } 195 196 // ErrorCode implements driver.ErrorCode. 197 func (k *keeper) ErrorCode(err error) gcerrors.ErrorCode { 198 return gcerr.GRPCCode(err) 199 } 200 201 // KeeperOptions controls Keeper behaviors. 202 // It is provided for future extensibility. 203 type KeeperOptions struct{}