2019-09-23 21:30:20 +03:00

219 lines
6.3 KiB
Go

// Copyright 2019 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package awskms provides a secrets implementation backed by AWS KMS.
// Use OpenKeeper to construct a *secrets.Keeper.
//
// URLs
//
// For secrets.OpenKeeper, awskms registers for the scheme "awskms".
// The default URL opener will use an AWS session with the default credentials
// and configuration; see https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
// for more details.
// To customize the URL opener, or for more details on the URL format,
// see URLOpener.
// See https://gocloud.dev/concepts/urls/ for background information.
//
// As
//
// awskms exposes the following type for As:
// - Error: awserr.Error
package awskms // import "gocloud.dev/secrets/awskms"
import (
"context"
"errors"
"fmt"
"net/url"
"path"
"sync"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/google/wire"
gcaws "gocloud.dev/aws"
"gocloud.dev/gcerrors"
"gocloud.dev/internal/gcerr"
"gocloud.dev/secrets"
)
func init() {
secrets.DefaultURLMux().RegisterKeeper(Scheme, new(lazySessionOpener))
}
// Set holds Wire providers for this package.
var Set = wire.NewSet(
wire.Struct(new(URLOpener), "ConfigProvider"),
Dial,
)
// Dial gets an AWS KMS service client.
func Dial(p client.ConfigProvider) (*kms.KMS, error) {
if p == nil {
return nil, errors.New("getting KMS service: no AWS session provided")
}
return kms.New(p), nil
}
// lazySessionOpener obtains the AWS session from the environment on the first
// call to OpenKeeperURL.
type lazySessionOpener struct {
init sync.Once
opener *URLOpener
err error
}
func (o *lazySessionOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
o.init.Do(func() {
sess, err := gcaws.NewDefaultSession()
if err != nil {
o.err = err
return
}
o.opener = &URLOpener{
ConfigProvider: sess,
}
})
if o.err != nil {
return nil, fmt.Errorf("open keeper %v: %v", u, o.err)
}
return o.opener.OpenKeeperURL(ctx, u)
}
// Scheme is the URL scheme awskms registers its URLOpener under on secrets.DefaultMux.
const Scheme = "awskms"
// URLOpener opens AWS KMS URLs like "awskms://keyID".
//
// The URL Host + Path are used as the key ID, which can be in the form of an
// Amazon Resource Name (ARN), alias name, or alias ARN. See
// https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html#find-cmk-id-arn
// for more details.
//
// See gocloud.dev/aws/ConfigFromURLParams for supported query parameters
// for overriding the aws.Session from the URL.
type URLOpener struct {
// ConfigProvider must be set to a non-nil value.
ConfigProvider client.ConfigProvider
// Options specifies the options to pass to OpenKeeper.
Options KeeperOptions
}
// OpenKeeperURL opens an AWS KMS Keeper based on u.
func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
configProvider := &gcaws.ConfigOverrider{
Base: o.ConfigProvider,
}
overrideCfg, err := gcaws.ConfigFromURLParams(u.Query())
if err != nil {
return nil, fmt.Errorf("open keeper %v: %v", u, err)
}
configProvider.Configs = append(configProvider.Configs, overrideCfg)
client, err := Dial(configProvider)
if err != nil {
return nil, err
}
return OpenKeeper(client, path.Join(u.Host, u.Path), &o.Options), nil
}
// OpenKeeper returns a *secrets.Keeper that uses AWS KMS.
// The key ID can be in the form of an Amazon Resource Name (ARN), alias
// name, or alias ARN. See
// https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html#find-cmk-id-arn
// for more details.
// See the package documentation for an example.
func OpenKeeper(client *kms.KMS, keyID string, opts *KeeperOptions) *secrets.Keeper {
return secrets.NewKeeper(&keeper{
keyID: keyID,
client: client,
})
}
type keeper struct {
keyID string
client *kms.KMS
}
// Decrypt decrypts the ciphertext into a plaintext.
func (k *keeper) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
result, err := k.client.Decrypt(&kms.DecryptInput{
CiphertextBlob: ciphertext,
})
if err != nil {
return nil, err
}
return result.Plaintext, nil
}
// Encrypt encrypts the plaintext into a ciphertext.
func (k *keeper) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) {
result, err := k.client.Encrypt(&kms.EncryptInput{
KeyId: aws.String(k.keyID),
Plaintext: plaintext,
})
if err != nil {
return nil, err
}
return result.CiphertextBlob, nil
}
// Close implements driver.Keeper.Close.
func (k *keeper) Close() error { return nil }
// ErrorAs implements driver.Keeper.ErrorAs.
func (k *keeper) ErrorAs(err error, i interface{}) bool {
e, ok := err.(awserr.Error)
if !ok {
return false
}
p, ok := i.(*awserr.Error)
if !ok {
return false
}
*p = e
return true
}
// ErrorCode implements driver.ErrorCode.
func (k *keeper) ErrorCode(err error) gcerrors.ErrorCode {
ae, ok := err.(awserr.Error)
if !ok {
return gcerr.Unknown
}
ec, ok := errorCodeMap[ae.Code()]
if !ok {
return gcerr.Unknown
}
return ec
}
var errorCodeMap = map[string]gcerrors.ErrorCode{
kms.ErrCodeNotFoundException: gcerrors.NotFound,
kms.ErrCodeInvalidCiphertextException: gcerrors.InvalidArgument,
kms.ErrCodeInvalidKeyUsageException: gcerrors.InvalidArgument,
kms.ErrCodeInternalException: gcerrors.Internal,
kms.ErrCodeInvalidStateException: gcerrors.FailedPrecondition,
kms.ErrCodeDisabledException: gcerrors.PermissionDenied,
kms.ErrCodeInvalidGrantTokenException: gcerrors.PermissionDenied,
kms.ErrCodeKeyUnavailableException: gcerrors.ResourceExhausted,
kms.ErrCodeDependencyTimeoutException: gcerrors.DeadlineExceeded,
}
// KeeperOptions controls Keeper behaviors.
// It is provided for future extensibility.
type KeeperOptions struct{}