Skip to content

Commit fd625b2

Browse files
committed
feat(azure): Add tests on AzureKeyvault backend #421
Signed-off-by: Yves Galante <[email protected]>
1 parent 5356a8c commit fd625b2

File tree

5 files changed

+245
-15
lines changed

5 files changed

+245
-15
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,10 @@ require (
261261
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
262262
)
263263

264+
require github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1
265+
264266
require (
265267
cloud.google.com/go/compute/metadata v0.2.3 // indirect
266-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect
267268
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
268269
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
269270
github.com/Azure/go-autorest v14.2.0+incompatible // indirect

pkg/backends/azurekeyvault.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,31 @@ package backends
33
import (
44
"context"
55
"fmt"
6-
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
6+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
7+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
78
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
89
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
910
"time"
1011
)
1112

1213
// AzureKeyVault is a struct for working with an Azure Key Vault backend
1314
type AzureKeyVault struct {
14-
Credential *azidentity.DefaultAzureCredential
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]
1522
}
1623

1724
// NewAzureKeyVaultBackend initializes a new Azure Key Vault backend
18-
func NewAzureKeyVaultBackend(credential *azidentity.DefaultAzureCredential) *AzureKeyVault {
25+
func NewAzureKeyVaultBackend(credential azcore.TokenCredential, clientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (*azsecrets.Client, error)) *AzureKeyVault {
1926
return &AzureKeyVault{
2027
Credential: credential,
28+
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error) {
29+
return clientBuilder(vaultURL, credential, options)
30+
},
2131
}
2232
}
2333

@@ -37,7 +47,7 @@ func (a *AzureKeyVault) GetSecrets(kvpath string, version string, _ map[string]s
3747

3848
verboseOptionalVersion("Azure Key Vault list all secrets from vault %s", version, kvpath)
3949

40-
client, err := azsecrets.NewClient(kvpath, a.Credential, nil)
50+
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
4151
if err != nil {
4252
return nil, err
4353
}
@@ -90,7 +100,7 @@ func (a *AzureKeyVault) GetIndividualSecret(kvpath, secret, version string, anno
90100
verboseOptionalVersion("Azure Key Vault getting individual secret %s from vault %s", version, secret, kvpath)
91101

92102
kvpath = fmt.Sprintf("https://%s.vault.azure.net", kvpath)
93-
client, err := azsecrets.NewClient(kvpath, a.Credential, nil)
103+
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
94104
if err != nil {
95105
return nil, err
96106
}

pkg/backends/azurekeyvault_test.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package backends_test
2+
3+
import (
4+
"context"
5+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
6+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
7+
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
8+
"github.com/argoproj-labs/argocd-vault-plugin/pkg/backends"
9+
"reflect"
10+
"testing"
11+
)
12+
13+
type mockClientProxy struct {
14+
simulateError string
15+
}
16+
17+
type MyError struct{}
18+
19+
func (m *MyError) Error() string {
20+
return "boom"
21+
}
22+
23+
func makeSecretProperties(id azsecrets.ID, enable bool) *azsecrets.SecretProperties {
24+
return &azsecrets.SecretProperties{
25+
ID: &id,
26+
Attributes: &azsecrets.SecretAttributes{
27+
Enabled: &enable,
28+
},
29+
}
30+
}
31+
32+
func makeResponse(id azsecrets.ID, value string, err error) (azsecrets.GetSecretResponse, error) {
33+
return azsecrets.GetSecretResponse{
34+
Secret: azsecrets.Secret{
35+
ID: &id,
36+
Value: &value,
37+
},
38+
}, err
39+
}
40+
41+
func newAzureKeyVaultBackendMock(simulateError string) *backends.AzureKeyVault {
42+
return &backends.AzureKeyVault{
43+
Credential: nil,
44+
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
45+
return &mockClientProxy{
46+
simulateError: simulateError,
47+
}, nil
48+
},
49+
}
50+
}
51+
52+
func (c *mockClientProxy) NewListSecretPropertiesPager(options *azsecrets.ListSecretPropertiesOptions) *runtime.Pager[azsecrets.ListSecretPropertiesResponse] {
53+
var pageCount = 0
54+
pager := runtime.NewPager(runtime.PagingHandler[azsecrets.ListSecretPropertiesResponse]{
55+
More: func(current azsecrets.ListSecretPropertiesResponse) bool {
56+
return pageCount == 0
57+
},
58+
Fetcher: func(ctx context.Context, current *azsecrets.ListSecretPropertiesResponse) (azsecrets.ListSecretPropertiesResponse, error) {
59+
pageCount++
60+
var a []*azsecrets.SecretProperties
61+
if c.simulateError == "fetch_error" {
62+
return azsecrets.ListSecretPropertiesResponse{}, &MyError{}
63+
} else if c.simulateError == "get_secret_error" {
64+
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/invalid/v2", true))
65+
}
66+
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/simple/v2", true))
67+
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/second/v2", true))
68+
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/disabled/v2", false))
69+
return azsecrets.ListSecretPropertiesResponse{
70+
SecretPropertiesListResult: azsecrets.SecretPropertiesListResult{
71+
Value: a,
72+
},
73+
}, nil
74+
},
75+
})
76+
return pager
77+
}
78+
79+
func (c *mockClientProxy) GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) {
80+
if name == "simple" && (version == "" || version == "v1") {
81+
return makeResponse("simple,v2", "a_value_v1", nil)
82+
} else if name == "simple" && version == "v2" {
83+
return makeResponse("simple,v2", "a_value_v2", nil)
84+
} else if name == "second" && (version == "" || version == "v2") {
85+
return makeResponse("simple,v2", "a_value_v2", nil)
86+
}
87+
return makeResponse("", "", &MyError{})
88+
}
89+
90+
func TestAzLogin(t *testing.T) {
91+
var keyVault = newAzureKeyVaultBackendMock("")
92+
var err = keyVault.Login()
93+
if err != nil {
94+
t.Fatalf("expected 0 errors but got: %s", err)
95+
}
96+
}
97+
98+
func TestAzGetSecret(t *testing.T) {
99+
var keyVault = newAzureKeyVaultBackendMock("")
100+
var data, err = keyVault.GetIndividualSecret("keyvault", "simple", "", nil)
101+
if err != nil {
102+
t.Fatalf("expected 0 errors but got: %s", err)
103+
}
104+
expected := "a_value_v1"
105+
if !reflect.DeepEqual(expected, data) {
106+
t.Errorf("expected: %s, got: %s.", expected, data)
107+
}
108+
}
109+
110+
func TestAzGetSecretWithVersion(t *testing.T) {
111+
var keyVault = newAzureKeyVaultBackendMock("")
112+
var data, err = keyVault.GetIndividualSecret("keyvault", "simple", "v2", nil)
113+
if err != nil {
114+
t.Fatalf("expected 0 errors but got: %s", err)
115+
}
116+
expected := "a_value_v2"
117+
if !reflect.DeepEqual(expected, data) {
118+
t.Errorf("expected: %s, got: %s.", expected, data)
119+
}
120+
}
121+
122+
func TestAzGetSecretWithWrongVersion(t *testing.T) {
123+
var keyVault = newAzureKeyVaultBackendMock("")
124+
var _, err = keyVault.GetIndividualSecret("keyvault", "simple", "v3", nil)
125+
if err == nil {
126+
t.Fatalf("expected 1 errors but got nil")
127+
}
128+
}
129+
130+
func TestAzGetSecretNotExist(t *testing.T) {
131+
var keyVault = newAzureKeyVaultBackendMock("")
132+
var _, err = keyVault.GetIndividualSecret("keyvault", "not_existing", "", nil)
133+
if err == nil {
134+
t.Fatalf("expected 1 errors but got nil")
135+
}
136+
}
137+
138+
func TestAzGetSecretBuilderError(t *testing.T) {
139+
var keyVault = &backends.AzureKeyVault{
140+
Credential: nil,
141+
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
142+
return nil, &MyError{}
143+
},
144+
}
145+
var _, err = keyVault.GetIndividualSecret("keyvault", "not_existing", "", nil)
146+
if err == nil {
147+
t.Fatalf("expected 1 errors but got nil")
148+
}
149+
}
150+
151+
func TestAzGetSecrets(t *testing.T) {
152+
var keyVault = newAzureKeyVaultBackendMock("")
153+
var res, err = keyVault.GetSecrets("keyvault", "", nil)
154+
155+
if err != nil {
156+
t.Fatalf("expected 0 errors but got: %s", err)
157+
}
158+
expected := map[string]interface{}{
159+
"simple": "a_value_v1",
160+
"second": "a_value_v2",
161+
}
162+
if !reflect.DeepEqual(res, expected) {
163+
t.Errorf("expected: %s, got: %s.", expected, res)
164+
}
165+
}
166+
167+
func TestAzGetSecretsWithError(t *testing.T) {
168+
var keyVault = newAzureKeyVaultBackendMock("fetch_error")
169+
var _, err = keyVault.GetSecrets("keyvault", "", nil)
170+
if err == nil {
171+
t.Fatalf("expected 1 errors but got nil")
172+
}
173+
}
174+
175+
func TestAzGetSecretsWithErrorOnGetSecret(t *testing.T) {
176+
var keyVault = newAzureKeyVaultBackendMock("get_secret_error")
177+
var _, err = keyVault.GetSecrets("keyvault", "", nil)
178+
if err == nil {
179+
t.Fatalf("expected 1 errors but got nil")
180+
}
181+
}
182+
183+
func TestAzGetSecretsBuilderError(t *testing.T) {
184+
var keyVault = &backends.AzureKeyVault{
185+
Credential: nil,
186+
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
187+
return nil, &MyError{}
188+
},
189+
}
190+
var _, err = keyVault.GetSecrets("keyvault", "", nil)
191+
if err == nil {
192+
t.Fatalf("expected 1 errors but got nil")
193+
}
194+
}
195+
196+
func TestAzGetSecretsVersionV1(t *testing.T) {
197+
var keyVault = newAzureKeyVaultBackendMock("")
198+
var res, err = keyVault.GetSecrets("keyvault", "v1", nil)
199+
200+
if err != nil {
201+
t.Fatalf("expected 0 errors but got: %s", err)
202+
}
203+
expected := map[string]interface{}{
204+
"simple": "a_value_v1",
205+
}
206+
if !reflect.DeepEqual(res, expected) {
207+
t.Errorf("expected: %s, got: %s.", expected, res)
208+
}
209+
}
210+
211+
func TestAzGetSecretsVersionV2(t *testing.T) {
212+
var keyVault = newAzureKeyVaultBackendMock("")
213+
var res, err = keyVault.GetSecrets("keyvault", "v2", nil)
214+
215+
if err != nil {
216+
t.Fatalf("expected 0 errors but got: %s", err)
217+
}
218+
expected := map[string]interface{}{
219+
"simple": "a_value_v2",
220+
"second": "a_value_v2",
221+
}
222+
if !reflect.DeepEqual(res, expected) {
223+
t.Errorf("expected: %s, got: %s.", expected, res)
224+
}
225+
}

pkg/config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
78
"os"
89
"strconv"
910
"strings"
@@ -191,7 +192,8 @@ func New(v *viper.Viper, co *Options) (*Config, error) {
191192
if err != nil {
192193
return nil, err
193194
}
194-
backend = backends.NewAzureKeyVaultBackend(cred)
195+
196+
backend = backends.NewAzureKeyVaultBackend(cred, azsecrets.NewClient)
195197
}
196198
case types.Sopsbackend:
197199
{

pkg/config/config_test.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,6 @@ func TestNewConfigMissingParameter(t *testing.T) {
418418
},
419419
"*backends.AWSSecretsManager",
420420
},
421-
{
422-
map[string]interface{}{
423-
"AVP_TYPE": "azurekeyvault",
424-
"AZURE_TENANT_ID": "test",
425-
"AZURE_CLIENT_ID": "test",
426-
},
427-
"*backends.AzureKeyVault",
428-
},
429421
{
430422
map[string]interface{}{
431423
"AVP_TYPE": "yandexcloudlockbox",

0 commit comments

Comments
 (0)