Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Azure AD Workload Identity. #548

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
YvesZelros marked this conversation as resolved.
Show resolved Hide resolved
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)
YvesZelros marked this conversation as resolved.
Show resolved Hide resolved
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