mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: add PushSecret and DeleteSecret to onepassword provider (#2646)
* feat: add PushSecret and DeleteSecret to onepassword provider Signed-off-by: Bryce Thuilot <bryce@thuilot.io> * refactor: clean code based on suggestions Signed-off-by: Bryce Thuilot <bryce@thuilot.io> * refactor: make suggested sonar cube changes Signed-off-by: Bryce Thuilot <bryce@thuilot.io> --------- Signed-off-by: Bryce Thuilot <bryce@thuilot.io>
This commit is contained in:
parent
0ac250dd2d
commit
0bb4feae4a
4 changed files with 912 additions and 35 deletions
|
@ -72,7 +72,7 @@ The following table show the support for features across different providers.
|
|||
| Alibaba Cloud KMS | | | | | x | | |
|
||||
| Oracle Vault | | | | | x | | |
|
||||
| Akeyless | x | x | | | x | | |
|
||||
| 1Password | x | | | | x | | |
|
||||
| 1Password | x | | | | x | x | x |
|
||||
| Generic Webhook | | | | | | | x |
|
||||
| senhasegura DSM | | | | | x | | |
|
||||
| Doppler | x | | | | x | | |
|
||||
|
|
|
@ -22,10 +22,13 @@ import (
|
|||
|
||||
// OnePasswordMockClient is a fake connect.Client.
|
||||
type OnePasswordMockClient struct {
|
||||
MockVaults map[string][]onepassword.Vault
|
||||
MockItems map[string][]onepassword.Item // ID and Title only
|
||||
MockItemFields map[string]map[string][]*onepassword.ItemField
|
||||
MockFileContents map[string][]byte
|
||||
MockVaults map[string][]onepassword.Vault
|
||||
MockItems map[string][]onepassword.Item // ID and Title only
|
||||
MockItemFields map[string]map[string][]*onepassword.ItemField
|
||||
MockFileContents map[string][]byte
|
||||
UpdateItemValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
|
||||
CreateItemValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
|
||||
DeleteItemValidateFunc func(*onepassword.Item, string) error
|
||||
}
|
||||
|
||||
// NewMockClient returns an instantiated mock client.
|
||||
|
@ -116,18 +119,27 @@ func (mockClient *OnePasswordMockClient) GetItemsByTitle(itemUUID, vaultUUID str
|
|||
return items, nil
|
||||
}
|
||||
|
||||
// CreateItem unused fake.
|
||||
func (mockClient *OnePasswordMockClient) CreateItem(_ *onepassword.Item, _ string) (*onepassword.Item, error) {
|
||||
// CreateItem will call a validation function if set.
|
||||
func (mockClient *OnePasswordMockClient) CreateItem(i *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
if mockClient.CreateItemValidateFunc != nil {
|
||||
return mockClient.CreateItemValidateFunc(i, s)
|
||||
}
|
||||
return &onepassword.Item{}, nil
|
||||
}
|
||||
|
||||
// UpdateItem unused fake.
|
||||
func (mockClient *OnePasswordMockClient) UpdateItem(_ *onepassword.Item, _ string) (*onepassword.Item, error) {
|
||||
// UpdateItem will call a validation function if set.
|
||||
func (mockClient *OnePasswordMockClient) UpdateItem(i *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
if mockClient.UpdateItemValidateFunc != nil {
|
||||
return mockClient.UpdateItemValidateFunc(i, s)
|
||||
}
|
||||
return &onepassword.Item{}, nil
|
||||
}
|
||||
|
||||
// DeleteItem unused fake.
|
||||
func (mockClient *OnePasswordMockClient) DeleteItem(_ *onepassword.Item, _ string) error {
|
||||
// DeleteItem will call a validation function if set.
|
||||
func (mockClient *OnePasswordMockClient) DeleteItem(i *onepassword.Item, s string) error {
|
||||
if mockClient.DeleteItemValidateFunc != nil {
|
||||
return mockClient.DeleteItemValidateFunc(i, s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ package onepassword
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
@ -45,17 +46,35 @@ const (
|
|||
errFetchK8sSecret = "could not fetch ConnectToken Secret: %w"
|
||||
errMissingToken = "missing Secret Token"
|
||||
errGetVault = "error finding 1Password Vault: %w"
|
||||
errExpectedOneItem = "expected one 1Password Item matching %w"
|
||||
errGetItem = "error finding 1Password Item: %w"
|
||||
errKeyNotFound = "key not found in 1Password Vaults: %w"
|
||||
errDocumentNotFound = "error finding 1Password Document: %w"
|
||||
errExpectedOneField = "expected one 1Password ItemField matching %w"
|
||||
errTagsNotImplemented = "'find.tags' is not implemented in the 1Password provider"
|
||||
errVersionNotImplemented = "'remoteRef.version' is not implemented in the 1Password provider"
|
||||
|
||||
documentCategory = "DOCUMENT"
|
||||
fieldsWithLabelFormat = "'%s' in '%s', got %d"
|
||||
incorrectCountFormat = "'%s', got %d"
|
||||
errGetItem = "error finding 1Password Item: %w"
|
||||
errUpdateItem = "error updating 1Password Item: %w"
|
||||
errDocumentNotFound = "error finding 1Password Document: %w"
|
||||
errTagsNotImplemented = "'find.tags' is not implemented in the 1Password provider"
|
||||
errVersionNotImplemented = "'remoteRef.version' is not implemented in the 1Password provider"
|
||||
errCreateItem = "error creating 1Password Item: %w"
|
||||
errDeleteItem = "error deleting 1Password Item: %w"
|
||||
// custom error messages.
|
||||
errKeyNotFoundMsg = "key not found in 1Password Vaults"
|
||||
errNoVaultsMsg = "no vaults found"
|
||||
errNoChangesMsg = "no changes made to 1Password Item"
|
||||
errExpectedOneItemMsg = "expected one 1Password Item matching"
|
||||
errExpectedOneFieldMsg = "expected one 1Password ItemField matching"
|
||||
errExpectedOneFieldMsgF = "%w: '%s' in '%s', got %d"
|
||||
|
||||
documentCategory = "DOCUMENT"
|
||||
)
|
||||
|
||||
// Custom Errors //.
|
||||
var (
|
||||
// ErrKeyNotFound is returned when a key is not found in the 1Password Vaults.
|
||||
ErrKeyNotFound = errors.New(errKeyNotFoundMsg)
|
||||
// ErrNoVaults is returned when no vaults are found in the 1Password provider.
|
||||
ErrNoVaults = errors.New(errNoVaultsMsg)
|
||||
// ErrExpectedOneField is returned when more than 1 field is found in the 1Password Vaults.
|
||||
ErrExpectedOneField = errors.New(errExpectedOneFieldMsg)
|
||||
// ErrExpectedOneItem is returned when more than 1 item is found in the 1Password Vaults.
|
||||
ErrExpectedOneItem = errors.New(errExpectedOneItemMsg)
|
||||
)
|
||||
|
||||
// ProviderOnePassword is a provider for 1Password.
|
||||
|
@ -152,13 +171,160 @@ func validateStore(store esv1beta1.GenericStore) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (provider *ProviderOnePassword) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
func deleteField(fields []*onepassword.ItemField, label string) ([]*onepassword.ItemField, error) {
|
||||
// This will always iterate over all items
|
||||
// but its done to ensure that two fields with the same label
|
||||
// exist resulting in undefined behavior
|
||||
var (
|
||||
found bool
|
||||
fieldsF = make([]*onepassword.ItemField, 0, len(fields))
|
||||
)
|
||||
for _, item := range fields {
|
||||
if item.Label == label {
|
||||
if found {
|
||||
return nil, ErrExpectedOneField
|
||||
}
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
fieldsF = append(fieldsF, item)
|
||||
}
|
||||
return fieldsF, nil
|
||||
}
|
||||
|
||||
// Not Implemented PushSecret.
|
||||
func (provider *ProviderOnePassword) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
func (provider *ProviderOnePassword) DeleteSecret(_ context.Context, ref esv1beta1.PushSecretRemoteRef) error {
|
||||
providerItem, err := provider.findItem(ref.GetRemoteKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
providerItem.Fields, err = deleteField(providerItem.Fields, ref.GetProperty())
|
||||
if err != nil {
|
||||
return fmt.Errorf(errUpdateItem, err)
|
||||
}
|
||||
|
||||
if len(providerItem.Fields) == 0 && len(providerItem.Files) == 0 && len(providerItem.Sections) == 0 {
|
||||
// Delete the item if there are no fields, files or sections
|
||||
if err = provider.client.DeleteItem(providerItem, providerItem.Vault.ID); err != nil {
|
||||
return fmt.Errorf(errDeleteItem, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = provider.client.UpdateItem(providerItem, providerItem.Vault.ID); err != nil {
|
||||
return fmt.Errorf(errDeleteItem, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
passwordLabel = "password"
|
||||
)
|
||||
|
||||
// createItem creates a new item in the first vault. If no vaults exist, it returns an error.
|
||||
func (provider *ProviderOnePassword) createItem(val []byte, ref esv1beta1.PushSecretData) error {
|
||||
// Get the first vault
|
||||
sortedVaults := sortVaults(provider.vaults)
|
||||
if len(sortedVaults) == 0 {
|
||||
return ErrNoVaults
|
||||
}
|
||||
vaultID := sortedVaults[0]
|
||||
// Get the label
|
||||
label := ref.GetProperty()
|
||||
if label == "" {
|
||||
label = passwordLabel
|
||||
}
|
||||
|
||||
// Create the item
|
||||
item := &onepassword.Item{
|
||||
Title: ref.GetRemoteKey(),
|
||||
Category: onepassword.Server,
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultID,
|
||||
},
|
||||
Fields: []*onepassword.ItemField{
|
||||
generateNewItemField(label, string(val)),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := provider.client.CreateItem(item, vaultID)
|
||||
return err
|
||||
}
|
||||
|
||||
// updateFieldValue updates the fields value of an item with the given label.
|
||||
// If the label does not exist, a new field is created. If the label exists but
|
||||
// the value is different, the value is updated. If the label exists and the
|
||||
// value is the same, nothing is done.
|
||||
func updateFieldValue(fields []*onepassword.ItemField, label, newVal string) ([]*onepassword.ItemField, error) {
|
||||
// This will always iterate over all items
|
||||
// but its done to ensure that two fields with the same label
|
||||
// exist resulting in undefined behavior
|
||||
var (
|
||||
found bool
|
||||
index int
|
||||
)
|
||||
for i, item := range fields {
|
||||
if item.Label == label {
|
||||
if found {
|
||||
return nil, ErrExpectedOneField
|
||||
}
|
||||
found = true
|
||||
index = i
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return append(fields, generateNewItemField(label, newVal)), nil
|
||||
}
|
||||
if field := fields[index]; newVal != field.Value {
|
||||
field.Value = newVal
|
||||
fields[index] = field
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// generateNewItemField generates a new item field with the given label and value.
|
||||
func generateNewItemField(label, newVal string) *onepassword.ItemField {
|
||||
field := &onepassword.ItemField{
|
||||
Label: label,
|
||||
Value: newVal,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
func (provider *ProviderOnePassword) PushSecret(_ context.Context, secret *corev1.Secret, ref esv1beta1.PushSecretData) error {
|
||||
val, ok := secret.Data[ref.GetSecretKey()]
|
||||
if !ok {
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
|
||||
title := ref.GetRemoteKey()
|
||||
providerItem, err := provider.findItem(title)
|
||||
if errors.Is(err, ErrKeyNotFound) {
|
||||
if err = provider.createItem(val, ref); err != nil {
|
||||
return fmt.Errorf(errCreateItem, err)
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
label := ref.GetProperty()
|
||||
if label == "" {
|
||||
label = passwordLabel
|
||||
}
|
||||
|
||||
providerItem.Fields, err = updateFieldValue(providerItem.Fields, label, string(val))
|
||||
if err != nil {
|
||||
return fmt.Errorf(errUpdateItem, err)
|
||||
}
|
||||
|
||||
if _, err = provider.client.UpdateItem(providerItem, providerItem.Vault.ID); err != nil {
|
||||
return fmt.Errorf(errUpdateItem, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSecret returns a single secret from the provider.
|
||||
|
@ -260,11 +426,11 @@ func (provider *ProviderOnePassword) findItem(name string) (*onepassword.Item, e
|
|||
case len(items) == 1:
|
||||
return provider.client.GetItemByUUID(items[0].ID, items[0].Vault.ID)
|
||||
case len(items) > 1:
|
||||
return nil, fmt.Errorf(errExpectedOneItem, fmt.Errorf(incorrectCountFormat, name, len(items)))
|
||||
return nil, fmt.Errorf("%w: '%s', got %d", ErrExpectedOneItem, name, len(items))
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errKeyNotFound, fmt.Errorf("%s in: %v", name, provider.vaults))
|
||||
return nil, fmt.Errorf("%w: %s in: %v", ErrKeyNotFound, name, provider.vaults)
|
||||
}
|
||||
|
||||
func (provider *ProviderOnePassword) getField(item *onepassword.Item, property string) ([]byte, error) {
|
||||
|
@ -275,7 +441,7 @@ func (provider *ProviderOnePassword) getField(item *onepassword.Item, property s
|
|||
}
|
||||
|
||||
if length := countFieldsWithLabel(fieldLabel, item.Fields); length != 1 {
|
||||
return nil, fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, fieldLabel, item.Title, length))
|
||||
return nil, fmt.Errorf("%w: '%s' in '%s', got %d", ErrExpectedOneField, fieldLabel, item.Title, length)
|
||||
}
|
||||
|
||||
// caution: do not use client.GetValue here because it has undesirable behavior on keys with a dot in them
|
||||
|
@ -297,7 +463,7 @@ func (provider *ProviderOnePassword) getFields(item *onepassword.Item, property
|
|||
continue
|
||||
}
|
||||
if length := countFieldsWithLabel(field.Label, item.Fields); length != 1 {
|
||||
return nil, fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, field.Label, item.Title, length))
|
||||
return nil, fmt.Errorf(errExpectedOneFieldMsgF, ErrExpectedOneField, field.Label, item.Title, length)
|
||||
}
|
||||
|
||||
// caution: do not use client.GetValue here because it has undesirable behavior on keys with a dot in them
|
||||
|
@ -315,7 +481,7 @@ func (provider *ProviderOnePassword) getAllFields(item onepassword.Item, ref esv
|
|||
item = *i
|
||||
for _, field := range item.Fields {
|
||||
if length := countFieldsWithLabel(field.Label, item.Fields); length != 1 {
|
||||
return fmt.Errorf(errExpectedOneField, fmt.Errorf(fieldsWithLabelFormat, field.Label, item.Title, length))
|
||||
return fmt.Errorf(errExpectedOneFieldMsgF, ErrExpectedOneField, field.Label, item.Title, length)
|
||||
}
|
||||
if ref.Name != nil {
|
||||
matcher, err := find.New(*ref.Name)
|
||||
|
|
|
@ -15,11 +15,14 @@ package onepassword
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
|
||||
|
@ -65,6 +68,8 @@ const (
|
|||
getAllSecretsErrFormat = "%s: onepassword.GetAllSecrets(...): -expected, +got:\n-%#v\n+%#v\n"
|
||||
validateStoreErrFormat = "%s: onepassword.validateStore(...): -expected, +got:\n-%#v\n+%#v\n"
|
||||
findItemErrFormat = "%s: onepassword.findItem(...): -expected, +got:\n-%#v\n+%#v\n"
|
||||
errFromErrMsgF = "%w: %s"
|
||||
errDoesNotMatchMsgF = "%s: error did not match: -expected, +got:\\n-%#v\\n+%#v\\n"
|
||||
)
|
||||
|
||||
func TestFindItem(t *testing.T) {
|
||||
|
@ -187,7 +192,7 @@ func TestFindItem(t *testing.T) {
|
|||
{
|
||||
checkNote: "no exist",
|
||||
findItemName: "my-item-no-exist",
|
||||
expectedErr: fmt.Errorf(errKeyNotFound, fmt.Errorf("my-item-no-exist in: map[my-vault:1]")),
|
||||
expectedErr: fmt.Errorf("%w: my-item-no-exist in: map[my-vault:1]", ErrKeyNotFound),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -208,7 +213,7 @@ func TestFindItem(t *testing.T) {
|
|||
{
|
||||
checkNote: "multiple match",
|
||||
findItemName: myItem,
|
||||
expectedErr: fmt.Errorf(errExpectedOneItem, fmt.Errorf("'my-item', got 2")),
|
||||
expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneItem, "'my-item', got 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -781,7 +786,7 @@ func TestGetSecret(t *testing.T) {
|
|||
Key: myItem,
|
||||
Property: key1,
|
||||
},
|
||||
expectedErr: fmt.Errorf(errExpectedOneField, fmt.Errorf("'key1' in 'my-item', got 2")),
|
||||
expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -948,7 +953,7 @@ func TestGetSecretMap(t *testing.T) {
|
|||
Key: myItem,
|
||||
},
|
||||
expectedMap: nil,
|
||||
expectedErr: fmt.Errorf(errExpectedOneField, fmt.Errorf("'key1' in 'my-item', got 2")),
|
||||
expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1407,3 +1412,697 @@ func TestHasUniqueVaultNumbers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRef struct {
|
||||
key string
|
||||
prop string
|
||||
secretKey string
|
||||
}
|
||||
|
||||
func (f fakeRef) GetRemoteKey() string {
|
||||
return f.key
|
||||
}
|
||||
|
||||
func (f fakeRef) GetProperty() string {
|
||||
return f.prop
|
||||
}
|
||||
|
||||
func (f fakeRef) GetSecretKey() string {
|
||||
return f.secretKey
|
||||
}
|
||||
|
||||
func (f fakeRef) GetMetadata() *apiextensionsv1.JSON {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateItem(t *testing.T, expectedItem, actualItem *onepassword.Item) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(expectedItem, actualItem) {
|
||||
t.Errorf("expected item %v, got %v", expectedItem, actualItem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderOnePasswordCreateItem(t *testing.T) {
|
||||
type testCase struct {
|
||||
vaults map[string]int
|
||||
expectedErr error
|
||||
setupNote string
|
||||
val []byte
|
||||
createValidateFunc func(*testing.T, *onepassword.Item, string) (*onepassword.Item, error)
|
||||
ref esv1beta1.PushSecretData
|
||||
}
|
||||
const vaultName = "vault1"
|
||||
|
||||
thridPartyErr := errors.New("third party error")
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
setupNote: "standard create",
|
||||
val: []byte("value"),
|
||||
ref: fakeRef{
|
||||
key: "testing",
|
||||
prop: "prop",
|
||||
},
|
||||
expectedErr: nil,
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
validateItem(t, &onepassword.Item{
|
||||
Title: "testing",
|
||||
Category: onepassword.Server,
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
Fields: []*onepassword.ItemField{
|
||||
generateNewItemField("prop", "value"),
|
||||
},
|
||||
}, item)
|
||||
return item, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "standard create with no property",
|
||||
val: []byte("value2"),
|
||||
ref: fakeRef{
|
||||
key: "testing2",
|
||||
prop: "",
|
||||
},
|
||||
vaults: map[string]int{
|
||||
vaultName: 2,
|
||||
},
|
||||
createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
validateItem(t, &onepassword.Item{
|
||||
Title: "testing2",
|
||||
Category: onepassword.Server,
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
Fields: []*onepassword.ItemField{
|
||||
generateNewItemField("password", "value2"),
|
||||
},
|
||||
}, item)
|
||||
return item, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "no vaults",
|
||||
val: []byte("value"),
|
||||
ref: fakeRef{
|
||||
key: "testing",
|
||||
prop: "prop",
|
||||
},
|
||||
vaults: map[string]int{},
|
||||
expectedErr: ErrNoVaults,
|
||||
createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
t.Errorf("onepassword.createItem(...): should not have been called")
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "error on create",
|
||||
val: []byte("testing"),
|
||||
ref: fakeRef{
|
||||
key: "another",
|
||||
prop: "property",
|
||||
},
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
expectedErr: thridPartyErr,
|
||||
createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
validateItem(t, &onepassword.Item{
|
||||
Title: "another",
|
||||
Category: onepassword.Server,
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
Fields: []*onepassword.ItemField{
|
||||
generateNewItemField("property", "testing"),
|
||||
},
|
||||
}, item)
|
||||
return nil, thridPartyErr
|
||||
},
|
||||
},
|
||||
}
|
||||
provider := &ProviderOnePassword{}
|
||||
for _, tc := range testCases {
|
||||
// setup
|
||||
mockClient := fake.NewMockClient()
|
||||
mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
i, e := tc.createValidateFunc(t, item, s)
|
||||
return i, e
|
||||
}
|
||||
provider.client = mockClient
|
||||
provider.vaults = tc.vaults
|
||||
|
||||
err := provider.createItem(tc.val, tc.ref)
|
||||
if !errors.Is(err, tc.expectedErr) {
|
||||
t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderOnePasswordDeleteItem(t *testing.T) {
|
||||
type testCase struct {
|
||||
inputFields []*onepassword.ItemField
|
||||
fieldName string
|
||||
expectedErr error
|
||||
expectedFields []*onepassword.ItemField
|
||||
setupNote string
|
||||
}
|
||||
|
||||
field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
|
||||
testCases := []testCase{
|
||||
{
|
||||
setupNote: "one field to remove",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
fieldName: field2,
|
||||
expectedFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "no fields to remove",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
fieldName: field4,
|
||||
expectedFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "multiple fields to remove",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeCreditCardType,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Type: onepassword.FieldTypeGender,
|
||||
},
|
||||
},
|
||||
fieldName: field3,
|
||||
expectedErr: ErrExpectedOneField,
|
||||
expectedFields: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// run the tests
|
||||
for _, tc := range testCases {
|
||||
actualOutput, err := deleteField(tc.inputFields, tc.fieldName)
|
||||
if len(actualOutput) != len(tc.expectedFields) {
|
||||
t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, tc.expectedErr) {
|
||||
t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
|
||||
}
|
||||
for i, check := range tc.expectedFields {
|
||||
if len(actualOutput) <= i {
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(check, actualOutput[i]) {
|
||||
t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFields(t *testing.T) {
|
||||
type testCase struct {
|
||||
inputFields []*onepassword.ItemField
|
||||
fieldName string
|
||||
newVal string
|
||||
expectedErr error
|
||||
expectedFields []*onepassword.ItemField
|
||||
setupNote string
|
||||
}
|
||||
|
||||
field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
|
||||
testCases := []testCase{
|
||||
{
|
||||
setupNote: "one field to update",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Value: value3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
fieldName: field2,
|
||||
newVal: "testing",
|
||||
expectedFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: "testing",
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Value: value3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "add field",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Value: value1,
|
||||
Label: field1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
},
|
||||
fieldName: field4,
|
||||
newVal: value4,
|
||||
expectedFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
Label: field4,
|
||||
Value: value4,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "no changes",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
},
|
||||
fieldName: field1,
|
||||
newVal: value1,
|
||||
expectedErr: nil,
|
||||
expectedFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "multiple fields to remove",
|
||||
inputFields: []*onepassword.ItemField{
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Value: value3,
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
{
|
||||
ID: field1,
|
||||
Label: field1,
|
||||
Value: value1,
|
||||
Type: onepassword.FieldTypeAddress,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Value: value3,
|
||||
Type: onepassword.FieldTypeCreditCardType,
|
||||
},
|
||||
{
|
||||
ID: field2,
|
||||
Label: field2,
|
||||
Value: value2,
|
||||
Type: onepassword.FieldTypeString,
|
||||
},
|
||||
{
|
||||
ID: field3,
|
||||
Label: field3,
|
||||
Value: value3,
|
||||
Type: onepassword.FieldTypeGender,
|
||||
},
|
||||
},
|
||||
fieldName: field3,
|
||||
expectedErr: ErrExpectedOneField,
|
||||
expectedFields: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// run the tests
|
||||
for _, tc := range testCases {
|
||||
actualOutput, err := updateFieldValue(tc.inputFields, tc.fieldName, tc.newVal)
|
||||
if len(actualOutput) != len(tc.expectedFields) {
|
||||
t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, tc.expectedErr) {
|
||||
t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
|
||||
}
|
||||
for i, check := range tc.expectedFields {
|
||||
if len(actualOutput) <= i {
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(check, actualOutput[i]) {
|
||||
t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateNewItemField(t *testing.T) {
|
||||
field := generateNewItemField("property", "testing")
|
||||
if !reflect.DeepEqual(field, &onepassword.ItemField{
|
||||
Label: "property",
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
Value: "testing",
|
||||
}) {
|
||||
t.Errorf("field did not match: -expected, +got:\n-%#v\n+%#v\n", &onepassword.ItemField{
|
||||
Label: "property",
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
Value: "testing",
|
||||
}, field)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderOnePasswordPushSecret(t *testing.T) {
|
||||
// Most logic is tested in the createItem and updateField functions
|
||||
// This test is just to make sure the correct functions are called.
|
||||
// the correct values are passed to them, and errors are propagated
|
||||
type testCase struct {
|
||||
vaults map[string]int
|
||||
expectedErr error
|
||||
setupNote string
|
||||
existingItems []onepassword.Item
|
||||
val *corev1.Secret
|
||||
existingItemsFields map[string][]*onepassword.ItemField
|
||||
createValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
|
||||
updateValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
|
||||
ref fakeRef
|
||||
}
|
||||
var (
|
||||
vaultName = "vault1"
|
||||
vault = onepassword.Vault{
|
||||
ID: vaultName,
|
||||
}
|
||||
)
|
||||
testCases := []testCase{
|
||||
{
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
expectedErr: ErrExpectedOneItem,
|
||||
setupNote: "find item error",
|
||||
existingItems: []onepassword.Item{
|
||||
{
|
||||
Title: key1,
|
||||
}, {
|
||||
Title: key1,
|
||||
}, // can be empty, testing for error with length
|
||||
},
|
||||
ref: fakeRef{
|
||||
key: key1,
|
||||
secretKey: key1,
|
||||
},
|
||||
val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
|
||||
},
|
||||
{
|
||||
setupNote: "create item error",
|
||||
expectedErr: ErrNoVaults,
|
||||
val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
|
||||
ref: fakeRef{secretKey: key1},
|
||||
vaults: nil,
|
||||
},
|
||||
{
|
||||
setupNote: "key not in data",
|
||||
expectedErr: ErrKeyNotFound,
|
||||
val: &corev1.Secret{Data: map[string][]byte{}},
|
||||
ref: fakeRef{secretKey: key1},
|
||||
vaults: nil,
|
||||
},
|
||||
{
|
||||
setupNote: "create item success",
|
||||
expectedErr: nil,
|
||||
val: &corev1.Secret{Data: map[string][]byte{
|
||||
key1: []byte("testing"),
|
||||
}},
|
||||
ref: fakeRef{
|
||||
key: key1,
|
||||
prop: "prop",
|
||||
secretKey: key1,
|
||||
},
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
validateItem(t, &onepassword.Item{
|
||||
Title: key1,
|
||||
Category: onepassword.Server,
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
Fields: []*onepassword.ItemField{
|
||||
generateNewItemField("prop", "testing"),
|
||||
},
|
||||
}, item)
|
||||
return item, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "update fields error",
|
||||
expectedErr: ErrExpectedOneField,
|
||||
val: &corev1.Secret{Data: map[string][]byte{
|
||||
"key2": []byte("testing"),
|
||||
}},
|
||||
ref: fakeRef{
|
||||
key: key1,
|
||||
prop: "prop",
|
||||
secretKey: "key2",
|
||||
},
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
existingItemsFields: map[string][]*onepassword.ItemField{
|
||||
key1: {
|
||||
{
|
||||
Label: "prop",
|
||||
},
|
||||
{
|
||||
Label: "prop",
|
||||
},
|
||||
},
|
||||
},
|
||||
existingItems: []onepassword.Item{
|
||||
{
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
ID: key1,
|
||||
Title: key1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
setupNote: "standard update",
|
||||
expectedErr: nil,
|
||||
val: &corev1.Secret{Data: map[string][]byte{
|
||||
"key3": []byte("testing2"),
|
||||
}},
|
||||
ref: fakeRef{
|
||||
key: key1,
|
||||
prop: "",
|
||||
secretKey: "key3",
|
||||
},
|
||||
vaults: map[string]int{
|
||||
vaultName: 1,
|
||||
},
|
||||
existingItemsFields: map[string][]*onepassword.ItemField{
|
||||
key1: {
|
||||
{
|
||||
Label: "not-prop",
|
||||
},
|
||||
},
|
||||
},
|
||||
updateValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
validateItem(t, &onepassword.Item{
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
ID: key1,
|
||||
Title: key1,
|
||||
Fields: []*onepassword.ItemField{
|
||||
{
|
||||
Label: "not-prop",
|
||||
},
|
||||
{
|
||||
Label: "password",
|
||||
Value: "testing2",
|
||||
Type: onepassword.FieldTypeConcealed,
|
||||
},
|
||||
},
|
||||
}, item)
|
||||
return nil, nil
|
||||
},
|
||||
existingItems: []onepassword.Item{
|
||||
{
|
||||
Vault: onepassword.ItemVault{
|
||||
ID: vaultName,
|
||||
},
|
||||
ID: key1,
|
||||
Title: key1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
provider := &ProviderOnePassword{}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.setupNote, func(t *testing.T) {
|
||||
// setup
|
||||
mockClient := fake.NewMockClient()
|
||||
mockClient.MockVaults = map[string][]onepassword.Vault{
|
||||
vaultName: {vault},
|
||||
}
|
||||
mockClient.MockItems = map[string][]onepassword.Item{
|
||||
vaultName: tc.existingItems,
|
||||
}
|
||||
mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
|
||||
vaultName: tc.existingItemsFields,
|
||||
}
|
||||
mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
return tc.createValidateFunc(item, s)
|
||||
}
|
||||
mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
|
||||
return tc.updateValidateFunc(item, s)
|
||||
}
|
||||
provider.client = mockClient
|
||||
provider.vaults = tc.vaults
|
||||
|
||||
err := provider.PushSecret(context.Background(), tc.val, tc.ref)
|
||||
if !errors.Is(err, tc.expectedErr) {
|
||||
t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue