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  }