github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/secrets/azurekeyvault/akv.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 azurekeyvault provides a secrets implementation backed by Azure KeyVault. 16 // See https://docs.microsoft.com/en-us/azure/key-vault/key-vault-whatis for more information. 17 // Use OpenKeeper to construct a *secrets.Keeper. 18 // 19 // # URLs 20 // 21 // For secrets.OpenKeeper, azurekeyvault registers for the scheme "azurekeyvault". 22 // The default URL opener will use DefaultClientMaker, which gets default credentials from the 23 // environment, unless the AZURE_KEYVAULT_AUTH_VIA_CLI environment variable is 24 // set to true, in which case it gets credentials from the "az" command line. 25 // 26 // To customize the URL opener, or for more details on the URL format, 27 // see URLOpener. 28 // See https://gocloud.dev/concepts/urls/ for background information. 29 // 30 // # As 31 // 32 // azurekeyvault exposes the following type for As: 33 // - Error: *azcore.ResponseError. 34 package azurekeyvault 35 36 import ( 37 "context" 38 "errors" 39 "fmt" 40 "net/url" 41 "os" 42 "path" 43 "regexp" 44 "strconv" 45 "strings" 46 47 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 48 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" 49 "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 50 "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" 51 "github.com/google/wire" 52 "gocloud.dev/gcerrors" 53 "gocloud.dev/internal/gcerr" 54 "gocloud.dev/internal/useragent" 55 "gocloud.dev/secrets" 56 ) 57 58 var ( 59 // Map of HTTP Status Code to go-cloud ErrorCode 60 errorCodeMap = map[int]gcerrors.ErrorCode{ 61 200: gcerrors.OK, 62 400: gcerrors.InvalidArgument, 63 401: gcerrors.PermissionDenied, 64 403: gcerrors.PermissionDenied, 65 404: gcerrors.NotFound, 66 408: gcerrors.DeadlineExceeded, 67 429: gcerrors.ResourceExhausted, 68 500: gcerrors.Internal, 69 501: gcerrors.Unimplemented, 70 } 71 ) 72 73 func init() { 74 secrets.DefaultURLMux().RegisterKeeper(Scheme, new(defaultDialer)) 75 } 76 77 // Set holds Wire providers for this package. 78 var Set = wire.NewSet( 79 DefaultClientMaker, 80 wire.Struct(new(URLOpener), "Client"), 81 ) 82 83 // ClientMakerT is the type of a function used to generate a Client. 84 type ClientMakerT func(keyVaultURI string) (*azkeys.Client, error) 85 86 // defaultDialer dials Azure KeyVault using DefaultClientMaker. 87 type defaultDialer struct{} 88 89 func (o *defaultDialer) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) { 90 opener := &URLOpener{ClientMaker: DefaultClientMaker} 91 return opener.OpenKeeperURL(ctx, u) 92 } 93 94 // Scheme is the URL scheme azurekeyvault registers its URLOpener under on secrets.DefaultMux. 95 const Scheme = "azurekeyvault" 96 97 // URLOpener opens Azure KeyVault URLs like 98 // "azurekeyvault://{keyvault-name}.vault.azure.net/keys/{key-name}/{key-version}?algorithm=RSA-OAEP-256". 99 // 100 // The "azurekeyvault" URL scheme is replaced with "https" to construct an Azure 101 // Key Vault keyID, as described in https://docs.microsoft.com/en-us/azure/key-vault/about-keys-secrets-and-certificates. 102 // The "/{key-version}"" suffix is optional; it defaults to the latest version. 103 // 104 // The "algorithm" query parameter sets the algorithm to use; see 105 // https://docs.microsoft.com/en-us/rest/api/keyvault/encrypt/encrypt#jsonwebkeyencryptionalgorithm 106 // for supported algorithms. It defaults to "RSA-OAEP-256". 107 // 108 // No other query parameters are supported. 109 type URLOpener struct { 110 // ClientMaker defaults to DefaultClientMaker. 111 ClientMaker ClientMakerT 112 113 // Options specifies the options to pass to OpenKeeper. 114 Options KeeperOptions 115 } 116 117 // OpenKeeperURL opens an Azure KeyVault Keeper based on u. 118 func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) { 119 q := u.Query() 120 algorithm := q.Get("algorithm") 121 if algorithm != "" { 122 o.Options.Algorithm = azkeys.JSONWebKeyEncryptionAlgorithm(algorithm) 123 q.Del("algorithm") 124 } 125 for param := range q { 126 return nil, fmt.Errorf("open keeper %v: invalid query parameter %q", u, param) 127 } 128 keyID := "https://" + path.Join(u.Host, u.Path) 129 return OpenKeeper(o.ClientMaker, keyID, &o.Options) 130 } 131 132 type keeper struct { 133 client *azkeys.Client 134 keyVaultURI string // unused, but for validation in tests 135 keyName string 136 keyVersion string 137 options *KeeperOptions 138 } 139 140 // KeeperOptions provides configuration options for encryption/decryption operations. 141 type KeeperOptions struct { 142 // Algorithm sets the encryption algorithm used. 143 // Defaults to "RSA-OAEP-256". 144 // See https://docs.microsoft.com/en-us/rest/api/keyvault/encrypt/encrypt#jsonwebkeyencryptionalgorithm 145 // for more details. 146 Algorithm azkeys.JSONWebKeyEncryptionAlgorithm 147 148 // EncryptOptions are passed through to Encrypt. 149 EncryptOptions *azkeys.EncryptOptions 150 151 // DecryptOptions are passed through to Decrypt. 152 DecryptOptions *azkeys.DecryptOptions 153 } 154 155 // DefaultClientMaker returns a function that constructs a KeyVault Client. 156 // By default it uses credentials from the environment; 157 // See https://docs.microsoft.com/en-us/go/azure/azure-sdk-go-authorization#use-environment-based-authentication. 158 // If the environment variable AZURE_KEYVAULT_AUTH_VIA_CLI is set to a truthy value, it 159 // uses credentials from the Azure CLI instead. 160 func DefaultClientMaker(keyVaultURI string) (*azkeys.Client, error) { 161 useCLI := false 162 useCLIStr := os.Getenv("AZURE_KEYVAULT_AUTH_VIA_CLI") 163 if useCLIStr != "" { 164 var err error 165 useCLI, err = strconv.ParseBool(useCLIStr) 166 if err != nil { 167 return nil, fmt.Errorf("invalid value %q for environment variable AZURE_KEYVAULT_AUTH_VIA_CLI: %v", useCLIStr, err) 168 } 169 } 170 var creds azcore.TokenCredential 171 var err error 172 if useCLI { 173 creds, err = azidentity.NewAzureCLICredential(nil) 174 } else { 175 creds, err = azidentity.NewEnvironmentCredential(nil) 176 } 177 if err != nil { 178 return nil, err 179 } 180 return azkeys.NewClient(keyVaultURI, creds, &azkeys.ClientOptions{ 181 ClientOptions: policy.ClientOptions{ 182 Telemetry: policy.TelemetryOptions{ 183 ApplicationID: useragent.AzureUserAgentPrefix("secrets"), 184 }, 185 }, 186 }), nil 187 } 188 189 var ( 190 // Note that the last binding may be just a key, or key/version. 191 keyIDRE = regexp.MustCompile(`^(https://.+\.vault\.(?:[a-z\d-.]+)/)keys/(.+)$`) 192 ) 193 194 // OpenKeeper returns a *secrets.Keeper that uses Azure keyVault. 195 // 196 // clientMaker is used to construct an azkeys.Client. 197 // 198 // keyID is a Azure Key Vault key identifier like "https://{keyvault-name}.vault.azure.net/keys/{key-name}/{key-version}". 199 // The "/{key-version}" suffix is optional; it defaults to the latest version. 200 // See https://docs.microsoft.com/en-us/azure/key-vault/about-keys-secrets-and-certificates 201 // for more details. 202 func OpenKeeper(clientMaker ClientMakerT, keyID string, opts *KeeperOptions) (*secrets.Keeper, error) { 203 drv, err := openKeeper(clientMaker, keyID, opts) 204 if err != nil { 205 return nil, err 206 } 207 return secrets.NewKeeper(drv), nil 208 } 209 210 func openKeeper(clientMaker ClientMakerT, keyID string, opts *KeeperOptions) (*keeper, error) { 211 if opts == nil { 212 opts = &KeeperOptions{} 213 } 214 if opts.Algorithm == "" { 215 opts.Algorithm = azkeys.JSONWebKeyEncryptionAlgorithmRSAOAEP256 216 } 217 matches := keyIDRE.FindStringSubmatch(keyID) 218 if len(matches) != 3 { 219 return nil, fmt.Errorf("invalid keyID %q; must match %v %v", keyID, keyIDRE, matches) 220 } 221 // matches[0] is the whole keyID, [1] is the keyVaultURI, and [2] is the key or the key/version. 222 keyVaultURI := matches[1] 223 parts := strings.SplitN(matches[2], "/", 2) 224 keyName := parts[0] 225 var keyVersion string 226 if len(parts) > 1 { 227 keyVersion = parts[1] 228 } 229 client, err := clientMaker(keyVaultURI) 230 if err != nil { 231 return nil, err 232 } 233 return &keeper{ 234 client: client, 235 keyVaultURI: keyVaultURI, 236 keyName: keyName, 237 keyVersion: keyVersion, 238 options: opts, 239 }, nil 240 } 241 242 // Encrypt encrypts the plaintext into a ciphertext. 243 func (k *keeper) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) { 244 keyOpsResult, err := k.client.Encrypt(ctx, k.keyName, k.keyVersion, azkeys.KeyOperationsParameters{ 245 Algorithm: &k.options.Algorithm, 246 Value: plaintext, 247 }, k.options.EncryptOptions) 248 if err != nil { 249 return nil, err 250 } 251 return keyOpsResult.Result, nil 252 } 253 254 // Decrypt decrypts the ciphertext into a plaintext. 255 func (k *keeper) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) { 256 keyOpsResult, err := k.client.Decrypt(ctx, k.keyName, k.keyVersion, azkeys.KeyOperationsParameters{ 257 Algorithm: &k.options.Algorithm, 258 Value: ciphertext, 259 }, k.options.DecryptOptions) 260 if err != nil { 261 return nil, err 262 } 263 return keyOpsResult.Result, nil 264 } 265 266 // Close implements driver.Keeper.Close. 267 func (k *keeper) Close() error { return nil } 268 269 // ErrorAs implements driver.Keeper.ErrorAs. 270 func (k *keeper) ErrorAs(err error, i interface{}) bool { 271 return errors.As(err, i) 272 } 273 274 // ErrorCode implements driver.ErrorCode. 275 func (k *keeper) ErrorCode(err error) gcerrors.ErrorCode { 276 re, ok := err.(*azcore.ResponseError) 277 if !ok { 278 return gcerr.Unknown 279 } 280 ec, ok := errorCodeMap[re.StatusCode] 281 if !ok { 282 return gcerr.Unknown 283 } 284 return ec 285 }