| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package conjur
- import (
- "context"
- "testing"
- "github.com/cyberark/conjur-api-go/conjurapi"
- "github.com/doodlesbykumbi/conjur-policy-go/pkg/conjurpolicy"
- "github.com/stretchr/testify/assert"
- "gopkg.in/yaml.v3"
- corev1 "k8s.io/api/core/v1"
- apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
- "github.com/external-secrets/external-secrets/providers/v1/conjur/fake"
- "github.com/external-secrets/external-secrets/runtime/esutils"
- )
- func TestDefaultPolicy(t *testing.T) {
- policy, err := conjurPolicy("secret1", []string{"foo", "bar", "baz"})
- assert.NoError(t, err)
- expected := `
- - !policy
- id: secret1
- body:
- - !group
- id: delegation/consumers
- annotations:
- managed-by: "external-secrets"
- editable: "true"
- - !variable
- id: foo
- annotations:
- managed-by: "external-secrets"
- - !variable
- id: bar
- annotations:
- managed-by: "external-secrets"
- - !variable
- id: baz
- annotations:
- managed-by: "external-secrets"
- - !permit
- resource: !variable foo
- role: !group delegation/consumers
- privileges: [ read, execute ]
- - !permit
- resource: !variable bar
- role: !group delegation/consumers
- privileges: [ read, execute ]
- - !permit
- resource: !variable baz
- role: !group delegation/consumers
- privileges: [ read, execute ]`
- // roundtrip the expected output through a unmarshal/marshal to remove any formatting related issues
- p := conjurpolicy.PolicyStatements{}
- err = yaml.Unmarshal([]byte(expected), &p)
- assert.NoError(t, err)
- exp, err := yaml.Marshal(p)
- assert.NoError(t, err)
- assert.Equal(t, exp, policy)
- }
- type RemoteRef struct {
- RemoteKey string
- Property string
- SecretKey string
- }
- func (r RemoteRef) GetRemoteKey() string {
- return r.RemoteKey
- }
- func (r RemoteRef) GetProperty() string {
- return r.Property
- }
- func (r RemoteRef) GetMetadata() *apiextensionsv1.JSON {
- return nil
- }
- func (r RemoteRef) GetSecretKey() string {
- return r.SecretKey
- }
- func TestPushSecret(t *testing.T) {
- tests := []struct {
- name string
- secretValue []byte
- remoteRef RemoteRef
- expectedPolicy string
- expectedVar string
- expectedVal string
- }{
- {
- name: "Push specified value to property",
- secretValue: []byte("password123"),
- remoteRef: RemoteRef{
- SecretKey: "password",
- RemoteKey: "data/vault/eso/db",
- Property: "password",
- },
- expectedVar: "data/vault/eso/db/password",
- expectedVal: "password123",
- expectedPolicy: `- !policy
- id: db
- body:
- - !group
- id: delegation/consumers
- annotations:
- editable: "true"
- managed-by: external-secrets
- - !variable
- id: password
- annotations:
- managed-by: external-secrets
- - !permit
- role: !group delegation/consumers
- privileges: [read, execute]
- resource: !variable password
- `,
- },
- {
- name: "Push all values to a single property",
- secretValue: []byte("password123"),
- remoteRef: RemoteRef{
- RemoteKey: "data/vault/eso/db",
- Property: "password",
- },
- expectedVar: "data/vault/eso/db/password",
- expectedVal: `{"password":"password123"}`,
- },
- {
- name: "Push all values, unspecified property",
- secretValue: []byte("password123"),
- remoteRef: RemoteRef{
- RemoteKey: "data/vault/eso/db",
- },
- expectedVar: "data/vault/eso/db/password",
- expectedVal: "password123",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mockClient := &fake.ConjurMockClient{}
- provider := &Client{
- store: makeAPIKeySecretStore(svcURL, "conjur-hostid", "conjur-apikey", "myconjuraccount"),
- kube: clientfake.NewClientBuilder().
- WithObjects(makeFakeAPIKeySecrets()...).Build(),
- namespace: "default",
- client: mockClient,
- }
- kubeSecret := &corev1.Secret{
- Data: map[string][]byte{
- "password": tt.secretValue,
- },
- }
- err := provider.PushSecret(context.Background(), kubeSecret, tt.remoteRef)
- if err != nil {
- t.Fatalf("PushSecret failed: %v", err)
- }
- if len(mockClient.AddSecretCalls) != 1 {
- t.Errorf("expected 1 AddSecret call, got %d", len(mockClient.AddSecretCalls))
- } else {
- call := mockClient.AddSecretCalls[0]
- if call.Variable != tt.expectedVar {
- t.Errorf("expected var %s, got %s", tt.expectedVar, call.Variable)
- }
- if call.Value != tt.expectedVal {
- t.Errorf("expected value %s, got %s", tt.expectedVal, call.Value)
- }
- }
- if tt.expectedPolicy != "" {
- if len(mockClient.LoadPolicyCalls) == 0 {
- t.Error("expected a LoadPolicy call but none occurred")
- } else {
- policy := mockClient.LoadPolicyCalls[0].Policy
- assert.Equal(t, tt.expectedPolicy, policy)
- }
- }
- })
- }
- }
- func TestCheckSecrets(t *testing.T) {
- tests := []struct {
- name string
- conjurSecretName string
- conjurVars []string
- secretData map[string]string
- property string
- key string
- setupMock func(m *fake.ConjurMockClient)
- expectedUpdates []string
- }{
- {
- name: "Unmanaged secret (should skip despite drift)",
- conjurSecretName: "db",
- conjurVars: []string{"pass"},
- secretData: map[string]string{"pass": "new-value"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretDetails = map[string]*conjurapi.StaticSecretResponse{
- "db/pass": {
- StaticSecret: conjurapi.StaticSecret{
- Annotations: map[string]string{"owner": "manual"},
- },
- },
- }
- m.SecretValues = map[string][]byte{"db/pass": []byte("old-value")}
- },
- expectedUpdates: []string{},
- },
- {
- name: "Managed secret with drift (should update)",
- conjurSecretName: "db",
- conjurVars: []string{"pass"},
- secretData: map[string]string{"pass": "new-value"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"db/pass": []byte("old-value")}
- },
- expectedUpdates: []string{"pass"},
- },
- {
- name: "Managed secret, no drift (should skip)",
- conjurSecretName: "db",
- conjurVars: []string{"pass"},
- secretData: map[string]string{"pass": "same-value"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"db/pass": []byte("same-value")}
- },
- expectedUpdates: []string{},
- },
- {
- name: "Property + Key: Value differs (should update)",
- conjurSecretName: "api",
- conjurVars: []string{"token"},
- property: "credential",
- key: "apikey",
- secretData: map[string]string{"apikey": "new-val"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"api/token": []byte("old-val")}
- },
- expectedUpdates: []string{"token"},
- },
- {
- name: "Property + Key: Value matches (should skip)",
- conjurSecretName: "api",
- conjurVars: []string{"token"},
- property: "credential",
- key: "apikey",
- secretData: map[string]string{"apikey": "same-val"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"api/token": []byte("same-val")}
- },
- expectedUpdates: []string{},
- },
- {
- name: "Property only (JSON): Matches marshaled data (should skip)",
- conjurSecretName: "app",
- conjurVars: []string{"config"},
- property: "json-blob",
- key: "",
- secretData: map[string]string{"user": "admin", "port": "8080"},
- setupMock: func(m *fake.ConjurMockClient) {
- marshaled, _ := esutils.JSONMarshal(map[string]string{"user": "admin", "port": "8080"})
- m.SecretValues = map[string][]byte{"app/config": marshaled}
- },
- expectedUpdates: []string{},
- },
- {
- name: "Property only (JSON): Data differs (should update)",
- conjurSecretName: "app",
- conjurVars: []string{"config"},
- property: "json-blob",
- key: "",
- secretData: map[string]string{"user": "admin"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"app/config": []byte(`{"user":"old"}`)}
- },
- expectedUpdates: []string{"config"},
- },
- {
- name: "Key-only (Flat): Value matches (should skip)",
- conjurSecretName: "db",
- conjurVars: []string{"username"},
- property: "",
- secretData: map[string]string{"username": "admin"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"db/username": []byte("admin")}
- },
- expectedUpdates: []string{},
- },
- {
- name: "Key-only (Flat): Value differs (should update)",
- conjurSecretName: "db",
- conjurVars: []string{"username"},
- property: "",
- secretData: map[string]string{"username": "new-admin"},
- setupMock: func(m *fake.ConjurMockClient) {
- m.SecretValues = map[string][]byte{"db/username": []byte("old-admin")}
- },
- expectedUpdates: []string{"username"},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- mock := &fake.ConjurMockClient{
- SecretDetails: make(map[string]*conjurapi.StaticSecretResponse),
- SecretValues: make(map[string][]byte),
- }
- tt.setupMock(mock)
- updates, err := checkSecrets(mock, tt.conjurSecretName, tt.conjurVars, tt.secretData, tt.property, tt.key)
- assert.NoError(t, err)
- assert.Equal(t, tt.expectedUpdates, updates)
- })
- }
- }
|