1package gcpkms
2
3import (
4	"context"
5	"fmt"
6	"path"
7	"sort"
8	"strings"
9	"sync"
10	"time"
11
12	"github.com/gammazero/workerpool"
13	"github.com/golang/protobuf/ptypes/duration"
14	"github.com/golang/protobuf/ptypes/timestamp"
15	"github.com/hashicorp/errwrap"
16	"github.com/hashicorp/vault/sdk/framework"
17	"github.com/hashicorp/vault/sdk/logical"
18	"google.golang.org/api/iterator"
19	"google.golang.org/genproto/protobuf/field_mask"
20
21	multierror "github.com/hashicorp/go-multierror"
22	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
23	grpccodes "google.golang.org/grpc/codes"
24	grpcstatus "google.golang.org/grpc/status"
25)
26
27func (b *backend) pathKeys() *framework.Path {
28	return &framework.Path{
29		Pattern: "keys/?$",
30
31		HelpSynopsis:    "List named keys",
32		HelpDescription: "List the named keys available for use.",
33
34		Callbacks: map[logical.Operation]framework.OperationFunc{
35			logical.ListOperation: withFieldValidator(b.pathKeysList),
36		},
37	}
38}
39
40func (b *backend) pathKeysCRUD() *framework.Path {
41	return &framework.Path{
42		Pattern: "keys/" + framework.GenericNameRegex("key"),
43
44		HelpSynopsis: "Interact with crypto keys in Vault and Google Cloud KMS",
45		HelpDescription: `
46This endpoint is used for the CRUD operations for keys in Vault.
47
48To create a new key or to update an existing key, perform a write operation with
49the name of the key and the configured parameters below. Vault will also create
50or modify the underlying Google Cloud KMS crypto key and store a reference to
51it.
52
53    $ vault write gcpkms/keys/my-key \
54        key_ring="projects/my-project/locations/global/keyRings/vault" \
55        rotation_period="72h" \
56        labels="test=true"
57
58To read data about a Google Cloud KMS crypto key, including the key status and
59current primary key version, read from the path:
60
61    $ vault read gcpkms/keys/my-key
62
63To delete a key from both Vault and Google Cloud KMS, perform a delete operation
64on the name of the key. This will disable automatic rotation of the key in
65Google Cloud KMS, disable all crypto key versions for this crypto key in Google
66Cloud KMS, and delete Vault's reference to the crypto key.
67
68    $ vault delete gcpkms/keys/my-key
69
70For more information about any of the options, please see the parameter
71documentation below. `,
72
73		Fields: map[string]*framework.FieldSchema{
74			"key": &framework.FieldSchema{
75				Type: framework.TypeString,
76				Description: `
77Name of the key in Vault.
78`,
79			},
80
81			"algorithm": &framework.FieldSchema{
82				Type: framework.TypeString,
83				Description: `
84Algorithm to use for encryption, decryption, or signing. The value depends on
85the key purpose. The value cannot be changed after creation.
86
87For a key purpose of "encrypt_decrypt", the valid values are:
88
89	- symmetric_encryption (default)
90
91For a key purpose of "asymmetric_sign", valid values are:
92
93	- rsa_sign_pss_2048_sha256
94	- rsa_sign_pss_3072_sha256
95	- rsa_sign_pss_4096_sha256
96	- rsa_sign_pkcs1_2048_sha256
97	- rsa_sign_pkcs1_3072_sha256
98	- rsa_sign_pkcs1_4096_sha256
99	- ec_sign_p256_sha256
100	- ec_sign_p384_sha384
101
102For a key purpose of "asymmetric_decrypt", valid values are:
103
104	- rsa_decrypt_oaep_2048_sha256
105	- rsa_decrypt_oaep_3072_sha256
106	- rsa_decrypt_oaep_4096_sha256
107`,
108			},
109
110			"key_ring": &framework.FieldSchema{
111				Type: framework.TypeString,
112				Description: `
113Full Google Cloud resource ID of the key ring with the project and location
114(e.g. projects/my-project/locations/global/keyRings/my-keyring). If the given
115key ring does not exist, Vault will try to create it during a create operation.
116`,
117			},
118
119			"crypto_key": &framework.FieldSchema{
120				Type: framework.TypeString,
121				Description: `
122Name of the crypto key to use. If the given crypto key does not exist, Vault
123will try to create it. This defaults to the name of the key given to Vault as
124the parameter if unspecified.
125`,
126			},
127
128			"protection_level": &framework.FieldSchema{
129				Type: framework.TypeString,
130				Description: `
131Level of protection to use for the key management. Valid values are "software"
132and "hsm". The default value is "software". The value cannot be changed after
133creation.
134`,
135			},
136
137			"purpose": &framework.FieldSchema{
138				Type: framework.TypeString,
139				Description: `
140Purpose of the key. Valid options are "asymmetric_decrypt", "asymmetric_sign",
141and "encrypt_decrypt". The default value is "encrypt_decrypt". The value cannot
142be changed after creation.
143`,
144			},
145
146			"rotation_period": &framework.FieldSchema{
147				Type: framework.TypeDurationSecond,
148				Description: `
149Amount of time between crypto key version rotations. This is specified as a time
150duration value like 72h (72 hours). The smallest possible value is 24h. This
151value only applies to keys with a purpose of "encrypt_decrypt".
152`,
153			},
154
155			"labels": &framework.FieldSchema{
156				Type: framework.TypeKVPairs,
157				Description: `
158Arbitrary key=value label to apply to the crypto key. To specify multiple
159labels, specify this argument multiple times (e.g. labels="a=b" labels="c=d").
160`,
161			},
162		},
163
164		ExistenceCheck: b.pathKeysExistenceCheck,
165
166		Callbacks: map[logical.Operation]framework.OperationFunc{
167			logical.ReadOperation:   withFieldValidator(b.pathKeysRead),
168			logical.CreateOperation: withFieldValidator(b.pathKeysWrite),
169			logical.UpdateOperation: withFieldValidator(b.pathKeysWrite),
170			logical.DeleteOperation: withFieldValidator(b.pathKeysDelete),
171		},
172	}
173}
174
175// pathKeysExistenceCheck is used to check if a given key exists.
176func (b *backend) pathKeysExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
177	key := d.Get("key").(string)
178	if k, err := b.Key(ctx, req.Storage, key); err != nil || k == nil {
179		return false, nil
180	}
181	return true, nil
182}
183
184// pathKeysRead corresponds to GET gcpkms/keys/:name and is used to show
185// information about the key.
186func (b *backend) pathKeysRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
187	key := d.Get("key").(string)
188
189	k, err := b.Key(ctx, req.Storage, key)
190	if err != nil {
191		if err == ErrKeyNotFound {
192			return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
193		}
194		return nil, err
195	}
196
197	kmsClient, closer, err := b.KMSClient(req.Storage)
198	if err != nil {
199		return nil, err
200	}
201	defer closer()
202
203	cryptoKey, err := kmsClient.GetCryptoKey(ctx, &kmspb.GetCryptoKeyRequest{
204		Name: k.CryptoKeyID,
205	})
206	if err != nil {
207		return nil, errwrap.Wrapf("failed to read crypto key: {{err}}", err)
208	}
209
210	data := map[string]interface{}{
211		"id":      cryptoKey.Name,
212		"purpose": purposeToString(cryptoKey.Purpose),
213	}
214
215	if len(cryptoKey.Labels) > 0 {
216		data["labels"] = cryptoKey.Labels
217	}
218	if cryptoKey.NextRotationTime != nil {
219		data["next_rotation_time_seconds"] = cryptoKey.NextRotationTime.Seconds
220	}
221	if cryptoKey.RotationSchedule != nil {
222		if t, ok := cryptoKey.RotationSchedule.(*kmspb.CryptoKey_RotationPeriod); ok && t.RotationPeriod != nil {
223			data["rotation_schedule_seconds"] = t.RotationPeriod.Seconds
224		}
225	}
226	if cryptoKey.Primary != nil {
227		data["primary_version"] = path.Base(cryptoKey.Primary.Name)
228		data["state"] = strings.ToLower(cryptoKey.Primary.State.String())
229	}
230	if vt := cryptoKey.VersionTemplate; vt != nil {
231		data["protection_level"] = protectionLevelToString(vt.ProtectionLevel)
232		data["algorithm"] = algorithmToString(vt.Algorithm)
233	}
234
235	return &logical.Response{
236		Data: data,
237	}, nil
238}
239
240// pathKeysList corresponds to LIST gcpkms/keys and is used to list all keys
241// in the system.
242func (b *backend) pathKeysList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
243	keys, err := b.Keys(ctx, req.Storage)
244	if err != nil {
245		return nil, err
246	}
247	return logical.ListResponse(keys), nil
248}
249
250// pathKeysWrite corresponds to PUT/POST gcpkms/keys/create/:key and creates a
251// new GCP KMS key and registers it for use in Vault.
252func (b *backend) pathKeysWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
253	kmsClient, closer, err := b.KMSClient(req.Storage)
254	if err != nil {
255		return nil, err
256	}
257	defer closer()
258
259	key := d.Get("key").(string)
260	keyRing := d.Get("key_ring").(string)
261	labels := d.Get("labels").(map[string]string)
262
263	// Default crypto key name to the key name if unspecified
264	cryptoKey := d.Get("crypto_key").(string)
265	if cryptoKey == "" {
266		cryptoKey = key
267	}
268
269	// Base key
270	ck := &kmspb.CryptoKey{
271		Labels:          labels,
272		VersionTemplate: new(kmspb.CryptoKeyVersionTemplate),
273	}
274
275	// Set purpose if given
276	if v, ok := d.GetOk("purpose"); ok {
277		if req.Operation == logical.UpdateOperation {
278			return nil, errImmutable("purpose")
279		}
280
281		purpose, ok := keyPurposes[strings.ToLower(v.(string))]
282		if !ok {
283			return nil, logical.CodedError(400, fmt.Sprintf(
284				"unknown purpose %q, valid purposes are %q", v, keyPurposeNames()))
285		}
286		ck.Purpose = purpose
287	} else {
288		ck.Purpose = kmspb.CryptoKey_ENCRYPT_DECRYPT
289	}
290
291	// Set algorithm if given
292	if v, ok := d.GetOk("algorithm"); ok {
293		algorithm, ok := keyAlgorithms[strings.ToLower(v.(string))]
294		if !ok {
295			return nil, logical.CodedError(400, fmt.Sprintf(
296				"unknown algorithm %q, valid algorithms are %q", v, keyAlgorithmNames()))
297		}
298		ck.VersionTemplate.Algorithm = algorithm
299	} else {
300		if ck.Purpose == kmspb.CryptoKey_ENCRYPT_DECRYPT {
301			ck.VersionTemplate.Algorithm = kmspb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION
302		} else {
303			return nil, errMissingFields("algorithm")
304		}
305	}
306
307	// Set the protection level
308	if v, ok := d.GetOk("protection_level"); ok {
309		if req.Operation == logical.UpdateOperation {
310			return nil, errImmutable("protection level")
311		}
312
313		protectionLevel, ok := keyProtectionLevels[strings.ToLower(v.(string))]
314		if !ok {
315			return nil, logical.CodedError(400, fmt.Sprintf(
316				"unknown protection level %q, valid protection levels are %q", v, keyProtectionLevelNames()))
317		}
318		ck.VersionTemplate.ProtectionLevel = protectionLevel
319	} else {
320		ck.VersionTemplate.ProtectionLevel = kmspb.ProtectionLevel_SOFTWARE
321	}
322
323	// Set rotation period
324	if v, ok := d.GetOk("rotation_period"); ok {
325		t := int64(v.(int))
326
327		ck.RotationSchedule = &kmspb.CryptoKey_RotationPeriod{
328			RotationPeriod: &duration.Duration{
329				Seconds: int64(t),
330			},
331		}
332
333		ck.NextRotationTime = &timestamp.Timestamp{
334			Seconds: time.Now().UTC().Add(time.Duration(t) * time.Second).Unix(),
335		}
336	}
337
338	// Check if the key ring exists
339	kr, err := kmsClient.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{
340		Name: keyRing,
341	})
342	if err != nil {
343		if terr, ok := grpcstatus.FromError(err); ok && terr.Code() == grpccodes.NotFound {
344			// Key ring does not exist, try to create it
345			kr, err = kmsClient.CreateKeyRing(ctx, &kmspb.CreateKeyRingRequest{
346				Parent:    path.Dir(path.Dir(keyRing)),
347				KeyRingId: path.Base(keyRing),
348			})
349			if err != nil {
350				return nil, errwrap.Wrapf("failed to create key ring: {{err}}", err)
351			}
352		} else {
353			return nil, errwrap.Wrapf("failed to check if key ring exists: {{err}}", err)
354		}
355	}
356
357	resp, err := kmsClient.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{
358		Parent:      kr.Name,
359		CryptoKeyId: cryptoKey,
360		CryptoKey:   ck,
361	})
362	if err != nil {
363		if terr, ok := grpcstatus.FromError(err); ok && terr.Code() == grpccodes.AlreadyExists {
364			if req.Operation != logical.UpdateOperation {
365				resp := logical.ErrorResponse(
366					"cannot update a key that is not already registered - register the " +
367						"key first using the /keys/register endpoint, and then update any " +
368						"configuration fields.")
369				return resp, logical.ErrPermissionDenied
370			}
371
372			var paths []string
373			ck.Name = fmt.Sprintf("%s/cryptoKeys/%s", kr.Name, cryptoKey)
374
375			if ck.Labels != nil {
376				paths = append(paths, "labels")
377			}
378
379			if ck.RotationSchedule != nil {
380				paths = append(paths, "rotation_period")
381			}
382
383			if ck.NextRotationTime != nil {
384				paths = append(paths, "next_rotation_time")
385			}
386
387			resp, err = kmsClient.UpdateCryptoKey(ctx, &kmspb.UpdateCryptoKeyRequest{
388				CryptoKey: ck,
389				UpdateMask: &field_mask.FieldMask{
390					Paths: paths,
391				},
392			})
393			if err != nil {
394				return nil, errwrap.Wrapf("failed to update crypto key: {{err}}", err)
395			}
396		} else {
397			return nil, errwrap.Wrapf("failed to create crypto key: {{err}}", err)
398		}
399	}
400
401	// Save it
402	entry, err := logical.StorageEntryJSON("keys/"+key, &Key{
403		Name:        key,
404		CryptoKeyID: resp.Name,
405	})
406	if err != nil {
407		return nil, errwrap.Wrapf("failed to create storage entry: {{err}}", err)
408	}
409	if err := req.Storage.Put(ctx, entry); err != nil {
410		return nil, errwrap.Wrapf("failed to write to storage: {{err}}", err)
411	}
412
413	return nil, nil
414}
415
416// pathKeysDelete corresponds to PUT/POST gcpkms/keys/delete/:key and deletes an
417// existing GCP KMS key and deregisters it from Vault.
418func (b *backend) pathKeysDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
419	kmsClient, closer, err := b.KMSClient(req.Storage)
420	if err != nil {
421		return nil, err
422	}
423	defer closer()
424
425	key := d.Get("key").(string)
426
427	k, err := b.Key(ctx, req.Storage, key)
428	if err != nil {
429		if err == ErrKeyNotFound {
430			return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
431		}
432		return nil, err
433	}
434
435	// Disable automatic key rotation
436	if _, err := kmsClient.UpdateCryptoKey(ctx, &kmspb.UpdateCryptoKeyRequest{
437		CryptoKey: &kmspb.CryptoKey{
438			Name:             k.CryptoKeyID,
439			NextRotationTime: nil,
440			RotationSchedule: nil,
441		},
442		UpdateMask: &field_mask.FieldMask{
443			Paths: []string{"next_rotation_time", "rotation_period"},
444		},
445	}); err != nil {
446		return nil, errwrap.Wrapf("failed to disable rotation on crypto key: {{err}}", err)
447	}
448
449	// Collect the list of all key versions
450	var ckvs []string
451	it := kmsClient.ListCryptoKeyVersions(ctx, &kmspb.ListCryptoKeyVersionsRequest{
452		Parent: k.CryptoKeyID,
453	})
454	for {
455		resp, err := it.Next()
456		if err != nil {
457			if err == iterator.Done {
458				break
459			}
460			return nil, errwrap.Wrapf("failed to list crypto key versions: {{err}}", err)
461		}
462
463		if resp.State != kmspb.CryptoKeyVersion_DESTROYED &&
464			resp.State != kmspb.CryptoKeyVersion_DESTROY_SCHEDULED {
465			ckvs = append(ckvs, resp.Name)
466		}
467	}
468
469	// Iterate over each key version and schedule deletion
470	var mu sync.Mutex
471	var errs *multierror.Error
472	wp := workerpool.New(25)
473	for _, ckv := range ckvs {
474		ckv := ckv
475
476		wp.Submit(func() {
477			if err := retryFib(func() error {
478				_, err := kmsClient.DestroyCryptoKeyVersion(ctx, &kmspb.DestroyCryptoKeyVersionRequest{
479					Name: ckv,
480				})
481				return err
482			}); err != nil {
483				mu.Lock()
484				errs = multierror.Append(errs, errwrap.Wrapf(fmt.Sprintf("failed to destroy crypto key version %s: {{err}}", ckv), err))
485				mu.Unlock()
486			}
487		})
488	}
489
490	wp.StopWait()
491
492	// Return errors if any happened
493	if err := errs.ErrorOrNil(); err != nil {
494		return nil, err
495	}
496
497	// Delete the key from our storage
498	if err := req.Storage.Delete(ctx, "keys/"+key); err != nil {
499		return nil, errwrap.Wrapf("failed to delete from storage: {{err}}", err)
500	}
501	return nil, nil
502}
503
504// keyPurposes is the list of purposes to key types
505var keyPurposes = map[string]kmspb.CryptoKey_CryptoKeyPurpose{
506	"asymmetric_decrypt": kmspb.CryptoKey_ASYMMETRIC_DECRYPT,
507	"asymmetric_sign":    kmspb.CryptoKey_ASYMMETRIC_SIGN,
508	"encrypt_decrypt":    kmspb.CryptoKey_ENCRYPT_DECRYPT,
509	"unspecified":        kmspb.CryptoKey_CRYPTO_KEY_PURPOSE_UNSPECIFIED,
510}
511
512// keyPurposeNames returns the list of key purposes.
513func keyPurposeNames() []string {
514	list := make([]string, 0, len(keyPurposes))
515	for k := range keyPurposes {
516		list = append(list, k)
517	}
518	sort.Strings(list)
519	return list
520}
521
522// purposeToString accepts a kmspb and maps that to the user readable purpose.
523// Instead of maintaining two maps, this iterates over the purposes map because
524// N will always be ridiculously small.
525func purposeToString(p kmspb.CryptoKey_CryptoKeyPurpose) string {
526	for k, v := range keyPurposes {
527		if p == v {
528			return k
529		}
530	}
531	return "unspecified"
532}
533
534// keyAlgorithms is the list of key algorithms.
535var keyAlgorithms = map[string]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
536	"symmetric_encryption":         kmspb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION,
537	"rsa_sign_pss_2048_sha256":     kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256,
538	"rsa_sign_pss_3072_sha256":     kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
539	"rsa_sign_pss_4096_sha256":     kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256,
540	"rsa_sign_pkcs1_2048_sha256":   kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256,
541	"rsa_sign_pkcs1_3072_sha256":   kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
542	"rsa_sign_pkcs1_4096_sha256":   kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
543	"rsa_decrypt_oaep_2048_sha256": kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_2048_SHA256,
544	"rsa_decrypt_oaep_3072_sha256": kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_3072_SHA256,
545	"rsa_decrypt_oaep_4096_sha256": kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_4096_SHA256,
546	"ec_sign_p256_sha256":          kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256,
547	"ec_sign_p384_sha384":          kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384,
548}
549
550// keyAlgorithmNames returns the list of key algorithms.
551func keyAlgorithmNames() []string {
552	list := make([]string, 0, len(keyAlgorithms))
553	for k := range keyAlgorithms {
554		list = append(list, k)
555	}
556	sort.Strings(list)
557	return list
558}
559
560// algorithmToString accepts a kmspb and maps that to the user readable algorithm.
561// Instead of maintaining two maps, this iterates over the algorithms map because
562// N will always be ridiculously small.
563func algorithmToString(p kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm) string {
564	for k, v := range keyAlgorithms {
565		if p == v {
566			return k
567		}
568	}
569	return "unspecified"
570}
571
572// keyProtectionLevels is the list of key protection levels.
573var keyProtectionLevels = map[string]kmspb.ProtectionLevel{
574	"hsm":      kmspb.ProtectionLevel_HSM,
575	"software": kmspb.ProtectionLevel_SOFTWARE,
576}
577
578// keyProtectionLevelNames returns the list of key protection levels.
579func keyProtectionLevelNames() []string {
580	list := make([]string, 0, len(keyProtectionLevels))
581	for k := range keyProtectionLevels {
582		list = append(list, k)
583	}
584	sort.Strings(list)
585	return list
586}
587
588// protectionLevelToString accepts a kmspb and maps that to the user readable algorithm.
589// Instead of maintaining two maps, this iterates over the algorithms map because
590// N will always be ridiculously small.
591func protectionLevelToString(p kmspb.ProtectionLevel) string {
592	for k, v := range keyProtectionLevels {
593		if p == v {
594			return k
595		}
596	}
597	return "unknown"
598}
599
600// errImmutable is a logical coded error that is returned when the user tries to
601// modfiy an immutable field.
602func errImmutable(s string) error {
603	return logical.CodedError(400, fmt.Sprintf("cannot change %s after key creation", s))
604}
605