Skip to content

Commit

Permalink
feat(azure): Support Azure Workload Identity #421
Browse files Browse the repository at this point in the history
Signed-off-by: Yves Galante <yves.galante@zelros.com>
  • Loading branch information
YvesZelros committed Jan 8, 2024
1 parent d3aa647 commit c9604b0
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 305 deletions.
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ go 1.21
require (
cloud.google.com/go/secretmanager v1.11.2
github.com/1Password/connect-sdk-go v1.5.3
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.0
github.com/IBM/go-sdk-core/v5 v5.14.1
github.com/IBM/secrets-manager-go-sdk v1.2.0
Expand Down Expand Up @@ -41,6 +43,9 @@ require (
cloud.google.com/go/kms v1.15.2 // indirect
cloud.google.com/go/monitoring v1.16.0 // indirect
filippo.io/age v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
Expand All @@ -51,6 +56,7 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/Jeffail/gabs v1.1.1 // indirect
Expand Down Expand Up @@ -124,6 +130,7 @@ require (
github.com/go-test/deep v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand Down Expand Up @@ -204,6 +211,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linode/linodego v0.7.1 // indirect
Expand Down Expand Up @@ -239,6 +247,7 @@ require (
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pires/go-proxyproto v0.6.1 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.1.0 h1:Q707j
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.1.0/go.mod h1:vjoxsjVnPwhjHZw4PuuhpgYlcxWl5tyNedLHUl0ulFA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1 h1:8TkzQBrN9PWIwo7ekdd696KpC6IfTltV2/F8qKKBWik=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1/go.mod h1:aprFpXPQiTyG5Rkz6Ot5pvU6y6YKg/AKYOcLCoxN0bk=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
Expand Down Expand Up @@ -1814,6 +1818,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
96 changes: 57 additions & 39 deletions pkg/backends/azurekeyvault.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@ package backends
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
"path"
"strings"
"time"
)

// AzureKeyVault is a struct for working with an Azure Key Vault backend
type AzureKeyVault struct {
Client keyvault.BaseClient
Credential azcore.TokenCredential
ClientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error)
}

type AzSecretsClient interface {
GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error)
NewListSecretPropertiesPager(options *azsecrets.ListSecretPropertiesOptions) *runtime.Pager[azsecrets.ListSecretPropertiesResponse]
}

// NewAzureKeyVaultBackend initializes a new Azure Key Vault backend
func NewAzureKeyVaultBackend(client keyvault.BaseClient) *AzureKeyVault {
func NewAzureKeyVaultBackend(credential azcore.TokenCredential, clientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (*azsecrets.Client, error)) *AzureKeyVault {
return &AzureKeyVault{
Client: client,
Credential: credential,
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error) {
return clientBuilder(vaultURL, credential, options)
},
}
}

Expand All @@ -29,59 +38,55 @@ func (a *AzureKeyVault) Login() error {

// GetSecrets gets secrets from Azure Key Vault and returns the formatted data
// For Azure Key Vault, `kvpath` is the unique name of your vault
// For Azure use the version here not make really sens as each secret have a different version but let support it
func (a *AzureKeyVault) GetSecrets(kvpath string, version string, _ map[string]string) (map[string]interface{}, error) {
kvpath = fmt.Sprintf("https://%s.vault.azure.net", kvpath)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

data := make(map[string]interface{})
verboseOptionalVersion("Azure Key Vault list all secrets from vault %s", version, kvpath)

utils.VerboseToStdErr("Azure Key Vault listing secrets in vault %v", kvpath)
secretList, err := a.Client.GetSecretsComplete(ctx, kvpath, nil)
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
if err != nil {
return nil, err
}

utils.VerboseToStdErr("Azure Key Vault list secrets response %v", secretList)
// Gather all secrets in Key Vault

for ; secretList.NotDone(); secretList.NextWithContext(ctx) {
secret := path.Base(*secretList.Value().ID)
if version == "" {
utils.VerboseToStdErr("Azure Key Vault getting secret %s from vault %s", secret, kvpath)
secretResp, err := a.Client.GetSecret(ctx, kvpath, secret, "")
if err != nil {
return nil, err
}
data := make(map[string]interface{})

utils.VerboseToStdErr("Azure Key Vault get unversioned secret response %v", secretResp)
data[secret] = *secretResp.Value
continue
pager := client.NewListSecretPropertiesPager(nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, err
}
// In Azure Key Vault the versions of a secret is first shown after running GetSecretVersions. So we need
// to loop through the versions for each secret in order to find the secret that has the specific version.
secretVersions, _ := a.Client.GetSecretVersionsComplete(ctx, kvpath, secret, nil)
for ; secretVersions.NotDone(); secretVersions.NextWithContext(ctx) {
secretVersion := secretVersions.Value()
for _, secretVersion := range page.Value {
// Azure Key Vault has ability to enable/disable a secret, so lets honour that
if !*secretVersion.Attributes.Enabled {
continue
}
// Secret version matched given version
if strings.Contains(*secretVersion.ID, version) {
utils.VerboseToStdErr("Azure Key Vault getting secret %s from vault %s at version %s", secret, kvpath, version)
secretResp, err := a.Client.GetSecret(ctx, kvpath, secret, version)
name := secretVersion.ID.Name()
// Secret version matched given version ?
if version == "" || secretVersion.ID.Version() == version {
verboseOptionalVersion("Azure Key Vault getting secret %s from vault %s", version, name, kvpath)
secret, err := client.GetSecret(ctx, name, version, nil)
if err != nil {
return nil, err
}

utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", secretResp)
data[secret] = *secretResp.Value
utils.VerboseToStdErr("Azure Key Vault get secret response %v", secret)
data[name] = *secret.Value
} else {
verboseOptionalVersion("Azure Key Vault getting secret %s from vault %s", version, name, kvpath)
secret, err := client.GetSecret(ctx, name, version, nil)
if err != nil || !*secretVersion.Attributes.Enabled {
utils.VerboseToStdErr("Azure Key Vault get versioned secret not found %s", err)
continue
}
utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", secret)
data[name] = *secret.Value
}
}
}

return data, nil
}

Expand All @@ -92,15 +97,28 @@ func (a *AzureKeyVault) GetIndividualSecret(kvpath, secret, version string, anno
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

utils.VerboseToStdErr("Azure Key Vault getting secret %s from vault %s at version %s", secret, kvpath, version)
verboseOptionalVersion("Azure Key Vault getting individual secret %s from vault %s", version, secret, kvpath)

kvpath = fmt.Sprintf("https://%s.vault.azure.net", kvpath)
data, err := a.Client.GetSecret(ctx, kvpath, secret, version)
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
if err != nil {
return nil, err
}

data, err := client.GetSecret(ctx, secret, version, nil)
if err != nil {
return nil, err
}

utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", data)
utils.VerboseToStdErr("Azure Key Vault get individual secret response %v", data)

return *data.Value, nil
}

func verboseOptionalVersion(format string, version string, message ...interface{}) {
if version == "" {
utils.VerboseToStdErr(format, message...)
} else {
utils.VerboseToStdErr(format+" at version %s", append(message, version)...)
}
}
Loading

0 comments on commit c9604b0

Please sign in to comment.