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 = ×tamp.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