// 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{}