-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for sourcing a
Config
from Azure KeyVault
- Loading branch information
1 parent
b69be41
commit 80bcbe2
Showing
4 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package auth_providers | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity" | ||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" | ||
) | ||
|
||
const ( | ||
EnvAzureVaultName = "AZURE_KEYVAULT_NAME" | ||
EnvAzureSecretName = "AZURE_SECRET_NAME" | ||
) | ||
|
||
// ConfigProviderAzureKeyVault is an Authenticator that uses Azure Key Vault for authentication. | ||
type ConfigProviderAzureKeyVault struct { | ||
SecretName string `json:"secret_name"` | ||
VaultName string `json:"vault_name"` | ||
DefaultCredential *azidentity.DefaultAzureCredential | ||
//TenantID string `json:"tenant_id;omitempty"` | ||
//SubscriptionID string `json:"subscription_id;omitempty"` | ||
//ResourceGroup string `json:"resource_group;omitempty"` | ||
} | ||
|
||
// NewConfigProviderAzureKeyVault creates a new instance of ConfigProviderAzureKeyVault. | ||
func NewConfigProviderAzureKeyVault() *ConfigProviderAzureKeyVault { | ||
return &ConfigProviderAzureKeyVault{} | ||
} | ||
|
||
// String returns a string representation of the ConfigProviderAzureKeyVault. | ||
func (a *ConfigProviderAzureKeyVault) String() string { | ||
return fmt.Sprintf("SecretName: %s, AzureVaultName: %s", a.SecretName, a.VaultName) | ||
} | ||
|
||
// WithSecretName sets the secret name for authentication. | ||
func (a *ConfigProviderAzureKeyVault) WithSecretName(secretName string) *ConfigProviderAzureKeyVault { | ||
a.SecretName = secretName | ||
return a | ||
} | ||
|
||
// WithVaultName sets the vault name for authentication. | ||
func (a *ConfigProviderAzureKeyVault) WithVaultName(vaultName string) *ConfigProviderAzureKeyVault { | ||
a.VaultName = vaultName | ||
return a | ||
} | ||
|
||
// Authenticate authenticates to Azure. | ||
func (a *ConfigProviderAzureKeyVault) Authenticate() error { | ||
// Create a context with a timeout | ||
ctx, cancel := context.WithTimeout(context.Background(), DefaultClientTimeout*time.Second) | ||
defer cancel() | ||
|
||
// Add custom metadata to context | ||
ctx = context.WithValue(ctx, contextKey("operation"), "AzureAuthenticate") | ||
|
||
// Try to authenticate using DefaultAzureCredential | ||
cred, err := azidentity.NewDefaultAzureCredential(nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to obtain a credential: %w", err) | ||
} | ||
a.DefaultCredential = cred | ||
return nil | ||
} | ||
|
||
// Validate validates the ConfigProviderAzureKeyVault. | ||
func (a *ConfigProviderAzureKeyVault) Validate() error { | ||
if a.SecretName == "" { | ||
// Check if the secret name is set in the environment | ||
if secretName := os.Getenv(EnvAzureSecretName); secretName != "" { | ||
a.SecretName = secretName | ||
} else { | ||
return fmt.Errorf("Azure KeyVault `SecretName` is required") | ||
} | ||
} | ||
if a.VaultName == "" { | ||
// Check if the vault name is set in the environment | ||
if vaultName := os.Getenv(EnvAzureVaultName); vaultName != "" { | ||
a.VaultName = vaultName | ||
} else { | ||
return fmt.Errorf("Azure KeyVault `VaultName` is required") | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// LoadConfigFromAzureKeyVault loads a Config type from Azure Key Vault. | ||
func (a *ConfigProviderAzureKeyVault) LoadConfigFromAzureKeyVault() (*Config, error) { | ||
if a.DefaultCredential == nil { | ||
aErr := a.Authenticate() | ||
if aErr != nil { | ||
return nil, aErr | ||
} | ||
} | ||
|
||
// Create a context with a timeout | ||
ctx, cancel := context.WithTimeout(context.Background(), DefaultClientTimeout*time.Second) | ||
defer cancel() | ||
|
||
// Add custom metadata to context | ||
ctx = context.WithValue(ctx, contextKey("operation"), "LoadConfigFromAzureKeyVault") | ||
ctx = context.WithValue(ctx, contextKey("vaultName"), a.VaultName) | ||
ctx = context.WithValue(ctx, contextKey("secretName"), a.SecretName) | ||
// Create a client to access the Azure Key Vault | ||
vaultURL := fmt.Sprintf("https://%s.vault.azure.net/", a.VaultName) | ||
client, cErr := azsecrets.NewClient(vaultURL, a.DefaultCredential, nil) | ||
if cErr != nil { | ||
return nil, cErr | ||
} | ||
|
||
// Retrieve the secret from the Azure Key Vault | ||
secretResp, sErr := client.GetSecret(ctx, a.SecretName, "", nil) | ||
if sErr != nil { | ||
return nil, sErr | ||
} | ||
|
||
// Check if the secret value is nil to avoid dereferencing a nil pointer | ||
if secretResp.Value == nil { | ||
return nil, fmt.Errorf("secret value for '%s' in vault '%s' is nil", a.SecretName, a.VaultName) | ||
} | ||
|
||
// Parse the secret value into a Config type | ||
var config Config | ||
if jErr := json.Unmarshal([]byte(*secretResp.Value), &config); jErr != nil { | ||
//attempt to unmarshal as a single server config | ||
var singleServerConfig Server | ||
if sjErr := json.Unmarshal([]byte(*secretResp.Value), &singleServerConfig); sjErr == nil { | ||
config.Servers = make(map[string]Server) | ||
config.Servers["default"] = singleServerConfig | ||
} else { | ||
return nil, jErr | ||
} | ||
} | ||
|
||
return &config, nil | ||
} | ||
|
||
// Example usage of ConfigProviderAzureKeyVault | ||
// | ||
// This example demonstrates how to use ConfigProviderAzureKeyVault to load a configuration from Azure Key Vault. | ||
// | ||
// func ExampleConfigProviderAzureKeyVault() { | ||
// provider := NewConfigProviderAzureKeyVault(). | ||
// WithSecretName("my-secret"). | ||
// WithVaultName("my-vault") | ||
// | ||
// err := provider.Authenticate() | ||
// if err != nil { | ||
// fmt.Println("Failed to authenticate:", err) | ||
// return | ||
// } | ||
// | ||
// config, err := provider.LoadConfigFromAzureKeyVault() | ||
// if err != nil { | ||
// fmt.Println("Failed to load config from Azure Key Vault:", err) | ||
// return | ||
// } | ||
// | ||
// fmt.Println("Loaded config from Azure Key Vault:", config) | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,64 @@ | ||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= | ||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= | ||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= | ||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= | ||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= | ||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= | ||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= | ||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= | ||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw= | ||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0/go.mod h1:XD3DIOOVgBCO03OleB1fHjgktVRFxlT++KwKgIOewdM= | ||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= | ||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= | ||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= | ||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= | ||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= | ||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= | ||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | ||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= | ||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= | ||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= | ||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= | ||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= | ||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= | ||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= | ||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= | ||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= | ||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= | ||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= | ||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= | ||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= | ||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= | ||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= | ||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= | ||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= | ||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= | ||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= | ||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |