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