Skip to content

Commit 4a2fca9

Browse files
committed
feat(azure): Support Azure Workload Identity #421
Signed-off-by: Yves Galante <[email protected]>
1 parent b2d7f10 commit 4a2fca9

File tree

6 files changed

+287
-305
lines changed

6 files changed

+287
-305
lines changed

go.mod

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ go 1.21
55
require (
66
cloud.google.com/go/secretmanager v1.11.2
77
github.com/1Password/connect-sdk-go v1.5.3
8-
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
8+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1
9+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
10+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1
911
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.0
1012
github.com/IBM/go-sdk-core/v5 v5.14.1
1113
github.com/IBM/secrets-manager-go-sdk v1.2.0
@@ -41,6 +43,9 @@ require (
4143
cloud.google.com/go/kms v1.15.2 // indirect
4244
cloud.google.com/go/monitoring v1.16.0 // indirect
4345
filippo.io/age v1.0.0 // indirect
46+
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
47+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
48+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
4449
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
4550
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
4651
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
@@ -51,6 +56,7 @@ require (
5156
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
5257
github.com/Azure/go-autorest/logger v0.2.1 // indirect
5358
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
59+
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
5460
github.com/BurntSushi/toml v1.3.2 // indirect
5561
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
5662
github.com/Jeffail/gabs v1.1.1 // indirect
@@ -124,6 +130,7 @@ require (
124130
github.com/go-test/deep v1.1.0 // indirect
125131
github.com/gogo/protobuf v1.3.2 // indirect
126132
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
133+
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
127134
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
128135
github.com/golang/protobuf v1.5.3 // indirect
129136
github.com/golang/snappy v0.0.4 // indirect
@@ -204,6 +211,7 @@ require (
204211
github.com/json-iterator/go v1.1.12 // indirect
205212
github.com/kelseyhightower/envconfig v1.4.0 // indirect
206213
github.com/klauspost/compress v1.17.0 // indirect
214+
github.com/kylelemons/godebug v1.1.0 // indirect
207215
github.com/leodido/go-urn v1.2.3 // indirect
208216
github.com/lib/pq v1.10.9 // indirect
209217
github.com/linode/linodego v0.7.1 // indirect
@@ -239,6 +247,7 @@ require (
239247
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
240248
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
241249
github.com/pires/go-proxyproto v0.6.1 // indirect
250+
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
242251
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
243252
github.com/posener/complete v1.2.3 // indirect
244253
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.1.0 h1:Q707j
7676
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.1.0/go.mod h1:vjoxsjVnPwhjHZw4PuuhpgYlcxWl5tyNedLHUl0ulFA=
7777
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
7878
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
79+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1 h1:8TkzQBrN9PWIwo7ekdd696KpC6IfTltV2/F8qKKBWik=
80+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.0.1/go.mod h1:aprFpXPQiTyG5Rkz6Ot5pvU6y6YKg/AKYOcLCoxN0bk=
81+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
82+
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
7983
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
8084
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
8185
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
@@ -1814,6 +1818,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
18141818
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18151819
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18161820
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1821+
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18171822
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18181823
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18191824
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

pkg/backends/azurekeyvault.go

+57-39
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,31 @@ package backends
33
import (
44
"context"
55
"fmt"
6-
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
6+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
7+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
8+
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
79
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
8-
"path"
9-
"strings"
1010
"time"
1111
)
1212

1313
// AzureKeyVault is a struct for working with an Azure Key Vault backend
1414
type AzureKeyVault struct {
15-
Client keyvault.BaseClient
15+
Credential azcore.TokenCredential
16+
ClientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error)
17+
}
18+
19+
type AzSecretsClient interface {
20+
GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error)
21+
NewListSecretPropertiesPager(options *azsecrets.ListSecretPropertiesOptions) *runtime.Pager[azsecrets.ListSecretPropertiesResponse]
1622
}
1723

1824
// NewAzureKeyVaultBackend initializes a new Azure Key Vault backend
19-
func NewAzureKeyVaultBackend(client keyvault.BaseClient) *AzureKeyVault {
25+
func NewAzureKeyVaultBackend(credential azcore.TokenCredential, clientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (*azsecrets.Client, error)) *AzureKeyVault {
2026
return &AzureKeyVault{
21-
Client: client,
27+
Credential: credential,
28+
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error) {
29+
return clientBuilder(vaultURL, credential, options)
30+
},
2231
}
2332
}
2433

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

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

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

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

40-
utils.VerboseToStdErr("Azure Key Vault listing secrets in vault %v", kvpath)
41-
secretList, err := a.Client.GetSecretsComplete(ctx, kvpath, nil)
50+
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
4251
if err != nil {
4352
return nil, err
4453
}
4554

46-
utils.VerboseToStdErr("Azure Key Vault list secrets response %v", secretList)
47-
// Gather all secrets in Key Vault
48-
49-
for ; secretList.NotDone(); secretList.NextWithContext(ctx) {
50-
secret := path.Base(*secretList.Value().ID)
51-
if version == "" {
52-
utils.VerboseToStdErr("Azure Key Vault getting secret %s from vault %s", secret, kvpath)
53-
secretResp, err := a.Client.GetSecret(ctx, kvpath, secret, "")
54-
if err != nil {
55-
return nil, err
56-
}
55+
data := make(map[string]interface{})
5756

58-
utils.VerboseToStdErr("Azure Key Vault get unversioned secret response %v", secretResp)
59-
data[secret] = *secretResp.Value
60-
continue
57+
pager := client.NewListSecretPropertiesPager(nil)
58+
for pager.More() {
59+
page, err := pager.NextPage(ctx)
60+
if err != nil {
61+
return nil, err
6162
}
62-
// In Azure Key Vault the versions of a secret is first shown after running GetSecretVersions. So we need
63-
// to loop through the versions for each secret in order to find the secret that has the specific version.
64-
secretVersions, _ := a.Client.GetSecretVersionsComplete(ctx, kvpath, secret, nil)
65-
for ; secretVersions.NotDone(); secretVersions.NextWithContext(ctx) {
66-
secretVersion := secretVersions.Value()
63+
for _, secretVersion := range page.Value {
6764
// Azure Key Vault has ability to enable/disable a secret, so lets honour that
6865
if !*secretVersion.Attributes.Enabled {
6966
continue
7067
}
71-
// Secret version matched given version
72-
if strings.Contains(*secretVersion.ID, version) {
73-
utils.VerboseToStdErr("Azure Key Vault getting secret %s from vault %s at version %s", secret, kvpath, version)
74-
secretResp, err := a.Client.GetSecret(ctx, kvpath, secret, version)
68+
name := secretVersion.ID.Name()
69+
// Secret version matched given version ?
70+
if version == "" || secretVersion.ID.Version() == version {
71+
verboseOptionalVersion("Azure Key Vault getting secret %s from vault %s", version, name, kvpath)
72+
secret, err := client.GetSecret(ctx, name, version, nil)
7573
if err != nil {
7674
return nil, err
7775
}
78-
79-
utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", secretResp)
80-
data[secret] = *secretResp.Value
76+
utils.VerboseToStdErr("Azure Key Vault get secret response %v", secret)
77+
data[name] = *secret.Value
78+
} else {
79+
verboseOptionalVersion("Azure Key Vault getting secret %s from vault %s", version, name, kvpath)
80+
secret, err := client.GetSecret(ctx, name, version, nil)
81+
if err != nil || !*secretVersion.Attributes.Enabled {
82+
utils.VerboseToStdErr("Azure Key Vault get versioned secret not found %s", err)
83+
continue
84+
}
85+
utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", secret)
86+
data[name] = *secret.Value
8187
}
8288
}
8389
}
84-
8590
return data, nil
8691
}
8792

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

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

97102
kvpath = fmt.Sprintf("https://%s.vault.azure.net", kvpath)
98-
data, err := a.Client.GetSecret(ctx, kvpath, secret, version)
103+
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
data, err := client.GetSecret(ctx, secret, version, nil)
99109
if err != nil {
100110
return nil, err
101111
}
102112

103-
utils.VerboseToStdErr("Azure Key Vault get versioned secret response %v", data)
113+
utils.VerboseToStdErr("Azure Key Vault get individual secret response %v", data)
104114

105115
return *data.Value, nil
106116
}
117+
118+
func verboseOptionalVersion(format string, version string, message ...interface{}) {
119+
if version == "" {
120+
utils.VerboseToStdErr(format, message...)
121+
} else {
122+
utils.VerboseToStdErr(format+" at version %s", append(message, version)...)
123+
}
124+
}

0 commit comments

Comments
 (0)