Skip to content

Commit

Permalink
feat(azure): Add tests on AzureKeyvault backend #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 Sep 9, 2023
1 parent 5356a8c commit fd625b2
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 15 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,10 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
)

require github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1

require (
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // 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
Expand Down
20 changes: 15 additions & 5 deletions pkg/backends/azurekeyvault.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@ package backends
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"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"
"time"
)

// AzureKeyVault is a struct for working with an Azure Key Vault backend
type AzureKeyVault struct {
Credential *azidentity.DefaultAzureCredential
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(credential *azidentity.DefaultAzureCredential) *AzureKeyVault {
func NewAzureKeyVaultBackend(credential azcore.TokenCredential, clientBuilder func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (*azsecrets.Client, error)) *AzureKeyVault {
return &AzureKeyVault{
Credential: credential,
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (AzSecretsClient, error) {
return clientBuilder(vaultURL, credential, options)
},
}
}

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

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

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

kvpath = fmt.Sprintf("https://%s.vault.azure.net", kvpath)
client, err := azsecrets.NewClient(kvpath, a.Credential, nil)
client, err := a.ClientBuilder(kvpath, a.Credential, nil)
if err != nil {
return nil, err
}
Expand Down
225 changes: 225 additions & 0 deletions pkg/backends/azurekeyvault_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package backends_test

import (
"context"
"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/backends"
"reflect"
"testing"
)

type mockClientProxy struct {
simulateError string
}

type MyError struct{}

func (m *MyError) Error() string {
return "boom"
}

func makeSecretProperties(id azsecrets.ID, enable bool) *azsecrets.SecretProperties {
return &azsecrets.SecretProperties{
ID: &id,
Attributes: &azsecrets.SecretAttributes{
Enabled: &enable,
},
}
}

func makeResponse(id azsecrets.ID, value string, err error) (azsecrets.GetSecretResponse, error) {
return azsecrets.GetSecretResponse{
Secret: azsecrets.Secret{
ID: &id,
Value: &value,
},
}, err
}

func newAzureKeyVaultBackendMock(simulateError string) *backends.AzureKeyVault {
return &backends.AzureKeyVault{
Credential: nil,
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
return &mockClientProxy{
simulateError: simulateError,
}, nil
},
}
}

func (c *mockClientProxy) NewListSecretPropertiesPager(options *azsecrets.ListSecretPropertiesOptions) *runtime.Pager[azsecrets.ListSecretPropertiesResponse] {
var pageCount = 0
pager := runtime.NewPager(runtime.PagingHandler[azsecrets.ListSecretPropertiesResponse]{
More: func(current azsecrets.ListSecretPropertiesResponse) bool {
return pageCount == 0
},
Fetcher: func(ctx context.Context, current *azsecrets.ListSecretPropertiesResponse) (azsecrets.ListSecretPropertiesResponse, error) {
pageCount++
var a []*azsecrets.SecretProperties
if c.simulateError == "fetch_error" {
return azsecrets.ListSecretPropertiesResponse{}, &MyError{}
} else if c.simulateError == "get_secret_error" {
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/invalid/v2", true))
}
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/simple/v2", true))
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/second/v2", true))
a = append(a, makeSecretProperties("https://myvaultname.vault.azure.net/keys/disabled/v2", false))
return azsecrets.ListSecretPropertiesResponse{
SecretPropertiesListResult: azsecrets.SecretPropertiesListResult{
Value: a,
},
}, nil
},
})
return pager
}

func (c *mockClientProxy) GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) {
if name == "simple" && (version == "" || version == "v1") {
return makeResponse("simple,v2", "a_value_v1", nil)
} else if name == "simple" && version == "v2" {
return makeResponse("simple,v2", "a_value_v2", nil)
} else if name == "second" && (version == "" || version == "v2") {
return makeResponse("simple,v2", "a_value_v2", nil)
}
return makeResponse("", "", &MyError{})
}

func TestAzLogin(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var err = keyVault.Login()
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
}

func TestAzGetSecret(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var data, err = keyVault.GetIndividualSecret("keyvault", "simple", "", nil)
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
expected := "a_value_v1"
if !reflect.DeepEqual(expected, data) {
t.Errorf("expected: %s, got: %s.", expected, data)
}
}

func TestAzGetSecretWithVersion(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var data, err = keyVault.GetIndividualSecret("keyvault", "simple", "v2", nil)
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
expected := "a_value_v2"
if !reflect.DeepEqual(expected, data) {
t.Errorf("expected: %s, got: %s.", expected, data)
}
}

func TestAzGetSecretWithWrongVersion(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var _, err = keyVault.GetIndividualSecret("keyvault", "simple", "v3", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecretNotExist(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var _, err = keyVault.GetIndividualSecret("keyvault", "not_existing", "", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecretBuilderError(t *testing.T) {
var keyVault = &backends.AzureKeyVault{
Credential: nil,
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
return nil, &MyError{}
},
}
var _, err = keyVault.GetIndividualSecret("keyvault", "not_existing", "", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecrets(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var res, err = keyVault.GetSecrets("keyvault", "", nil)

if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
expected := map[string]interface{}{
"simple": "a_value_v1",
"second": "a_value_v2",
}
if !reflect.DeepEqual(res, expected) {
t.Errorf("expected: %s, got: %s.", expected, res)
}
}

func TestAzGetSecretsWithError(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("fetch_error")
var _, err = keyVault.GetSecrets("keyvault", "", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecretsWithErrorOnGetSecret(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("get_secret_error")
var _, err = keyVault.GetSecrets("keyvault", "", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecretsBuilderError(t *testing.T) {
var keyVault = &backends.AzureKeyVault{
Credential: nil,
ClientBuilder: func(vaultURL string, credential azcore.TokenCredential, options *azsecrets.ClientOptions) (backends.AzSecretsClient, error) {
return nil, &MyError{}
},
}
var _, err = keyVault.GetSecrets("keyvault", "", nil)
if err == nil {
t.Fatalf("expected 1 errors but got nil")
}
}

func TestAzGetSecretsVersionV1(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var res, err = keyVault.GetSecrets("keyvault", "v1", nil)

if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
expected := map[string]interface{}{
"simple": "a_value_v1",
}
if !reflect.DeepEqual(res, expected) {
t.Errorf("expected: %s, got: %s.", expected, res)
}
}

func TestAzGetSecretsVersionV2(t *testing.T) {
var keyVault = newAzureKeyVaultBackendMock("")
var res, err = keyVault.GetSecrets("keyvault", "v2", nil)

if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
expected := map[string]interface{}{
"simple": "a_value_v2",
"second": "a_value_v2",
}
if !reflect.DeepEqual(res, expected) {
t.Errorf("expected: %s, got: %s.", expected, res)
}
}
4 changes: 3 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -191,7 +192,8 @@ func New(v *viper.Viper, co *Options) (*Config, error) {
if err != nil {
return nil, err
}
backend = backends.NewAzureKeyVaultBackend(cred)

backend = backends.NewAzureKeyVaultBackend(cred, azsecrets.NewClient)
}
case types.Sopsbackend:
{
Expand Down
8 changes: 0 additions & 8 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,6 @@ func TestNewConfigMissingParameter(t *testing.T) {
},
"*backends.AWSSecretsManager",
},
{
map[string]interface{}{
"AVP_TYPE": "azurekeyvault",
"AZURE_TENANT_ID": "test",
"AZURE_CLIENT_ID": "test",
},
"*backends.AzureKeyVault",
},
{
map[string]interface{}{
"AVP_TYPE": "yandexcloudlockbox",
Expand Down

0 comments on commit fd625b2

Please sign in to comment.