github.com/cornelk/go-cloud@v0.17.1/secrets/awskms/kms.go (about)

     1  // Copyright 2019 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 awskms provides a secrets implementation backed by AWS KMS.
    16  // Use OpenKeeper to construct a *secrets.Keeper.
    17  //
    18  // URLs
    19  //
    20  // For secrets.OpenKeeper, awskms registers for the scheme "awskms".
    21  // The default URL opener will use an AWS session with the default credentials
    22  // and configuration; see https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
    23  // for more details.
    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  // awskms exposes the following type for As:
    31  //  - Error: awserr.Error
    32  package awskms // import "github.com/cornelk/go-cloud/secrets/awskms"
    33  
    34  import (
    35  	"context"
    36  	"errors"
    37  	"fmt"
    38  	"net/url"
    39  	"path"
    40  	"sync"
    41  
    42  	"github.com/aws/aws-sdk-go/aws"
    43  	"github.com/aws/aws-sdk-go/aws/awserr"
    44  	"github.com/aws/aws-sdk-go/aws/client"
    45  	"github.com/aws/aws-sdk-go/service/kms"
    46  	gcaws "github.com/cornelk/go-cloud/aws"
    47  	"github.com/cornelk/go-cloud/gcerrors"
    48  	"github.com/cornelk/go-cloud/internal/gcerr"
    49  	"github.com/cornelk/go-cloud/secrets"
    50  	"github.com/google/wire"
    51  )
    52  
    53  func init() {
    54  	secrets.DefaultURLMux().RegisterKeeper(Scheme, new(lazySessionOpener))
    55  }
    56  
    57  // Set holds Wire providers for this package.
    58  var Set = wire.NewSet(
    59  	wire.Struct(new(URLOpener), "ConfigProvider"),
    60  	Dial,
    61  )
    62  
    63  // Dial gets an AWS KMS service client.
    64  func Dial(p client.ConfigProvider) (*kms.KMS, error) {
    65  	if p == nil {
    66  		return nil, errors.New("getting KMS service: no AWS session provided")
    67  	}
    68  	return kms.New(p), nil
    69  }
    70  
    71  // lazySessionOpener obtains the AWS session from the environment on the first
    72  // call to OpenKeeperURL.
    73  type lazySessionOpener struct {
    74  	init   sync.Once
    75  	opener *URLOpener
    76  	err    error
    77  }
    78  
    79  func (o *lazySessionOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
    80  	o.init.Do(func() {
    81  		sess, err := gcaws.NewDefaultSession()
    82  		if err != nil {
    83  			o.err = err
    84  			return
    85  		}
    86  		o.opener = &URLOpener{
    87  			ConfigProvider: sess,
    88  		}
    89  	})
    90  	if o.err != nil {
    91  		return nil, fmt.Errorf("open keeper %v: %v", u, o.err)
    92  	}
    93  	return o.opener.OpenKeeperURL(ctx, u)
    94  }
    95  
    96  // Scheme is the URL scheme awskms registers its URLOpener under on secrets.DefaultMux.
    97  const Scheme = "awskms"
    98  
    99  // URLOpener opens AWS KMS URLs like "awskms://keyID".
   100  //
   101  // The URL Host + Path are used as the key ID, which can be in the form of an
   102  // Amazon Resource Name (ARN), alias name, or alias ARN. See
   103  // https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html#find-cmk-id-arn
   104  // for more details.
   105  //
   106  // See github.com/cornelk/go-cloud/aws/ConfigFromURLParams for supported query parameters
   107  // for overriding the aws.Session from the URL.
   108  type URLOpener struct {
   109  	// ConfigProvider must be set to a non-nil value.
   110  	ConfigProvider client.ConfigProvider
   111  
   112  	// Options specifies the options to pass to OpenKeeper.
   113  	Options KeeperOptions
   114  }
   115  
   116  // OpenKeeperURL opens an AWS KMS Keeper based on u.
   117  func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
   118  	configProvider := &gcaws.ConfigOverrider{
   119  		Base: o.ConfigProvider,
   120  	}
   121  	overrideCfg, err := gcaws.ConfigFromURLParams(u.Query())
   122  	if err != nil {
   123  		return nil, fmt.Errorf("open keeper %v: %v", u, err)
   124  	}
   125  	configProvider.Configs = append(configProvider.Configs, overrideCfg)
   126  	client, err := Dial(configProvider)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return OpenKeeper(client, path.Join(u.Host, u.Path), &o.Options), nil
   131  }
   132  
   133  // OpenKeeper returns a *secrets.Keeper that uses AWS KMS.
   134  // The key ID can be in the form of an Amazon Resource Name (ARN), alias
   135  // name, or alias ARN. See
   136  // https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html#find-cmk-id-arn
   137  // for more details.
   138  // See the package documentation for an example.
   139  func OpenKeeper(client *kms.KMS, keyID string, opts *KeeperOptions) *secrets.Keeper {
   140  	return secrets.NewKeeper(&keeper{
   141  		keyID:  keyID,
   142  		client: client,
   143  	})
   144  }
   145  
   146  type keeper struct {
   147  	keyID  string
   148  	client *kms.KMS
   149  }
   150  
   151  // Decrypt decrypts the ciphertext into a plaintext.
   152  func (k *keeper) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
   153  	result, err := k.client.Decrypt(&kms.DecryptInput{
   154  		CiphertextBlob: ciphertext,
   155  	})
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	return result.Plaintext, nil
   160  }
   161  
   162  // Encrypt encrypts the plaintext into a ciphertext.
   163  func (k *keeper) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) {
   164  	result, err := k.client.Encrypt(&kms.EncryptInput{
   165  		KeyId:     aws.String(k.keyID),
   166  		Plaintext: plaintext,
   167  	})
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	return result.CiphertextBlob, nil
   172  }
   173  
   174  // Close implements driver.Keeper.Close.
   175  func (k *keeper) Close() error { return nil }
   176  
   177  // ErrorAs implements driver.Keeper.ErrorAs.
   178  func (k *keeper) ErrorAs(err error, i interface{}) bool {
   179  	e, ok := err.(awserr.Error)
   180  	if !ok {
   181  		return false
   182  	}
   183  	p, ok := i.(*awserr.Error)
   184  	if !ok {
   185  		return false
   186  	}
   187  	*p = e
   188  	return true
   189  }
   190  
   191  // ErrorCode implements driver.ErrorCode.
   192  func (k *keeper) ErrorCode(err error) gcerrors.ErrorCode {
   193  	ae, ok := err.(awserr.Error)
   194  	if !ok {
   195  		return gcerr.Unknown
   196  	}
   197  	ec, ok := errorCodeMap[ae.Code()]
   198  	if !ok {
   199  		return gcerr.Unknown
   200  	}
   201  	return ec
   202  }
   203  
   204  var errorCodeMap = map[string]gcerrors.ErrorCode{
   205  	kms.ErrCodeNotFoundException:          gcerrors.NotFound,
   206  	kms.ErrCodeInvalidCiphertextException: gcerrors.InvalidArgument,
   207  	kms.ErrCodeInvalidKeyUsageException:   gcerrors.InvalidArgument,
   208  	kms.ErrCodeInternalException:          gcerrors.Internal,
   209  	kms.ErrCodeInvalidStateException:      gcerrors.FailedPrecondition,
   210  	kms.ErrCodeDisabledException:          gcerrors.PermissionDenied,
   211  	kms.ErrCodeInvalidGrantTokenException: gcerrors.PermissionDenied,
   212  	kms.ErrCodeKeyUnavailableException:    gcerrors.ResourceExhausted,
   213  	kms.ErrCodeDependencyTimeoutException: gcerrors.DeadlineExceeded,
   214  }
   215  
   216  // KeeperOptions controls Keeper behaviors.
   217  // It is provided for future extensibility.
   218  type KeeperOptions struct{}