Skip to content

Commit dd0b2f8

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

File tree

5 files changed

+266
-15
lines changed

5 files changed

+266
-15
lines changed

go.mod

+2-1
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

+15-5
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

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

pkg/config/config.go

+3-1
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

-8
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)