github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/security/keycrypt/kms/kms.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache-2.0 3 // license that can be found in the LICENSE file. 4 5 // Package kms implements a Keycrypt using AWS's KMS service and S3. 6 // Secrets are stored using the AWS-provided s3crypto package, which 7 // uses a KMS data key to perform client-side encryption and 8 // decryption of keys. 9 // 10 // For each key stored, s3crypto retrieves a data encryption key 11 // which is derived from a master key stored securely in KMS's HSMs. 12 // KMS returns both an encrypted and a plaintext version of the data 13 // encryption key. The key is subsequently used to encrypt the 14 // keybundle and is then thrown away. The encrypted version of the 15 // key is stored together with the bundle. 16 // 17 // Access to Amazon's KMS is controlled by IAM security policies. 18 // 19 // When a bundle is retrieved, s3crypto asks KMS to decrypt the key 20 // that is stored with the bundle, which in turn is used to decrypt 21 // the bundle contents. 22 package kms 23 24 import ( 25 "bytes" 26 "fmt" 27 "io/ioutil" 28 "path" 29 30 "github.com/aws/aws-sdk-go/aws" 31 "github.com/aws/aws-sdk-go/aws/awserr" 32 "github.com/aws/aws-sdk-go/aws/session" 33 "github.com/aws/aws-sdk-go/service/kms" 34 "github.com/aws/aws-sdk-go/service/s3" 35 "github.com/aws/aws-sdk-go/service/s3/s3crypto" 36 "github.com/Schaudge/grailbase/security/keycrypt" 37 ) 38 39 const ( 40 // The prefix used for S3 bucket keys. This is defined so that 41 // we can support future versions which may make use of 42 // different representations and layouts. 43 prefix = "v1/" 44 ) 45 46 // CredentialsChainVerboseErrors is used to set 47 // aws.Config.CredentialsChainVerboseErrors when creating a kms session. 48 var CredentialsChainVerboseErrors = false 49 50 // DefaultRegion is used to set the the AWS region for looking up KMS keys. 51 var DefaultRegion = "us-west-2" 52 53 func init() { 54 keycrypt.RegisterFunc("kms", func(h string) keycrypt.Keycrypt { 55 sess := session.New(&aws.Config{ 56 Region: &DefaultRegion, 57 CredentialsChainVerboseErrors: &CredentialsChainVerboseErrors, 58 }) 59 return New(sess, h) 60 }) 61 } 62 63 var _ keycrypt.Keycrypt = (*Crypt)(nil) 64 65 // Crypt implements a Keycrypt using Amazon's KMS and S3 services. 66 type Crypt struct { 67 sess *session.Session 68 handler s3crypto.CipherDataGenerator 69 bucket string 70 } 71 72 // Create a new Keycrypt instance which uses Amazon's KMS to store 73 // key material securely. 74 func New(sess *session.Session, id string) *Crypt { 75 return &Crypt{ 76 sess: sess, 77 handler: s3crypto.NewKMSKeyGenerator(kms.New(sess), fmt.Sprintf("alias/%s", id)), 78 bucket: fmt.Sprintf("grail-keycrypt-%s", id), 79 } 80 } 81 82 func (c *Crypt) Lookup(name string) keycrypt.Secret { 83 return &secret{c, name} 84 } 85 86 type secret struct { 87 *Crypt 88 name string 89 } 90 91 func (s *secret) Get() ([]byte, error) { 92 svc := s3crypto.NewDecryptionClient(s.sess) 93 94 key := path.Join(prefix, s.name) 95 resp, err := svc.GetObject(&s3.GetObjectInput{ 96 Bucket: &s.bucket, 97 Key: &key, 98 }) 99 if err != nil { 100 if err, ok := err.(awserr.Error); ok && err.Code() == "NoSuchKey" { 101 return nil, keycrypt.ErrNoSuchSecret 102 } 103 return nil, err 104 } 105 106 p, err := ioutil.ReadAll(resp.Body) 107 resp.Body.Close() 108 return p, err 109 } 110 111 func (s *secret) Put(p []byte) error { 112 svc := s3crypto.NewEncryptionClient(s.sess, s3crypto.AESGCMContentCipherBuilder(s.handler)) 113 114 key := path.Join(prefix, s.name) 115 _, err := svc.PutObject(&s3.PutObjectInput{ 116 Body: bytes.NewReader(p), 117 Bucket: &s.bucket, 118 Key: &key, 119 }) 120 return err 121 }