| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055 |
- /*
- 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 beyondtrust
- import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
- "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication"
- "github.com/BeyondTrust/go-client-library-passwordsafe/api/logging"
- "github.com/BeyondTrust/go-client-library-passwordsafe/api/utils"
- "github.com/cenkalti/backoff/v4"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.uber.org/zap"
- v1 "k8s.io/api/core/v1"
- apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/tools/clientcmd"
- clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
- kubeclient "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
- esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- )
- const (
- errTestCase = "Test case Failed"
- fakeAPIURL = "https://example.com:443/BeyondTrust/api/public/v3/"
- apiKey = "fakeapikey00fakeapikeydd0000000000065b010f20fakeapikey0000000008700000a93fb5d74fddc0000000000000000000000000000000000000;runas=test_user"
- clientID = "12345678-25fg-4b05-9ced-35e7dd5093ae"
- clientSecret = "12345678-25fg-4b05-9ced-35e7dd5093ae"
- authConnectTokenPath = "/Auth/connect/token"
- authSignAppInPath = "/Auth/SignAppIn"
- secretsSafeFoldersPath = "/secrets-safe/folders/"
- secretsSafeSecretsPath = "/secrets-safe/secrets"
- folderSecretsPath = "/secrets-safe/folders/cb871861-8b40-4556-820c-1ca6d522adfa/secrets"
- apiVersion32 = "3.2"
- fakeClientSecret = "fake_client_secret"
- passwordKey = "password"
- testCredentialKey = "test-credential"
- testName = "test"
- )
- func createMockPasswordSafeClient(t *testing.T) kubeclient.Client {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"fake@beyondtrust.com"}`))
- if err != nil {
- t.Error(errTestCase)
- }
- case "/Auth/Signout":
- _, err := w.Write([]byte(``))
- if err != nil {
- t.Error(errTestCase)
- }
- case secretsSafeSecretsPath:
- _, err := w.Write([]byte(`[{"SecretType": "FILE", "Password": "credential_in_sub_3_password","Id": "12345678-07d6-4955-175a-08db047219ce","Title": "credential_in_sub_3"}]`))
- if err != nil {
- t.Error(errTestCase)
- }
- case "/secrets-safe/secrets/12345678-07d6-4955-175a-08db047219ce/file/download":
- _, err := w.Write([]byte(`fake_password`))
- if err != nil {
- t.Error(errTestCase)
- }
- default:
- http.NotFound(w, r)
- }
- }))
- t.Cleanup(server.Close)
- clientConfig := clientcmd.NewDefaultClientConfig(clientcmdapi.Config{
- Clusters: map[string]*clientcmdapi.Cluster{
- testName: {
- Server: server.URL,
- },
- },
- AuthInfos: map[string]*clientcmdapi.AuthInfo{
- testName: {
- Token: "token",
- },
- },
- Contexts: map[string]*clientcmdapi.Context{
- testName: {
- Cluster: testName,
- AuthInfo: testName,
- },
- },
- CurrentContext: testName,
- }, &clientcmd.ConfigOverrides{})
- restConfig, err := clientConfig.ClientConfig()
- assert.Nil(t, err)
- c, err := kubeclient.New(restConfig, kubeclient.Options{})
- assert.Nil(t, err)
- return c
- }
- func TestNewClient(t *testing.T) {
- type args struct {
- store esv1.SecretStore
- kube kubeclient.Client
- provider esv1.Provider
- }
- tests := []struct {
- name string
- nameSpace string
- args args
- validateErrorNil bool
- validateErrorText bool
- expectedErrorText string
- }{
- {
- name: "Client ok",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- ClientID: &esv1.BeyondTrustProviderSecretRef{
- Value: clientID,
- },
- ClientSecret: &esv1.BeyondTrustProviderSecretRef{
- Value: clientSecret,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: true,
- validateErrorText: false,
- },
- {
- name: "Bad Client Id",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- ClientID: &esv1.BeyondTrustProviderSecretRef{
- Value: "6138d050",
- },
- ClientSecret: &esv1.BeyondTrustProviderSecretRef{
- Value: clientSecret,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: false,
- validateErrorText: true,
- expectedErrorText: "error in Inputs: Error in field ClientId : min / 36.",
- },
- {
- name: "Bad Client Secret",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- ClientSecret: &esv1.BeyondTrustProviderSecretRef{
- Value: "8i7U0Yulabon8mTc",
- },
- ClientID: &esv1.BeyondTrustProviderSecretRef{
- Value: clientID,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: false,
- validateErrorText: true,
- expectedErrorText: "error in Inputs: Error in field ClientSecret : min / 36.",
- },
- {
- name: "Bad Separator",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- Separator: "//",
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- ClientID: &esv1.BeyondTrustProviderSecretRef{
- Value: clientID,
- },
- ClientSecret: &esv1.BeyondTrustProviderSecretRef{
- Value: clientSecret,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: false,
- validateErrorText: true,
- expectedErrorText: "error in Inputs: Error in field ClientId : min / 36.",
- },
- {
- name: "Time Out",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- Separator: "/",
- ClientTimeOutSeconds: 400,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- ClientID: &esv1.BeyondTrustProviderSecretRef{
- Value: clientID,
- },
- ClientSecret: &esv1.BeyondTrustProviderSecretRef{
- Value: clientSecret,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: false,
- validateErrorText: true,
- expectedErrorText: "error in Inputs: Error in field ClientTimeOutinSeconds : lte / 300.",
- },
- {
- name: "ApiKey ok",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- APIKey: &esv1.BeyondTrustProviderSecretRef{
- Value: apiKey,
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: true,
- validateErrorText: false,
- },
- {
- name: "Bad ApiKey",
- nameSpace: testName,
- args: args{
- store: esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Beyondtrust: &esv1.BeyondtrustProvider{
- Server: &esv1.BeyondtrustServer{
- APIURL: fakeAPIURL,
- RetrievalType: secretTypeSecret,
- },
- Auth: &esv1.BeyondtrustAuth{
- APIKey: &esv1.BeyondTrustProviderSecretRef{
- Value: "bad_api_key",
- },
- },
- },
- },
- },
- },
- kube: createMockPasswordSafeClient(t),
- provider: &Provider{},
- },
- validateErrorNil: false,
- validateErrorText: true,
- expectedErrorText: "error in Inputs: Error in field ApiKey : min / 128.",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := tt.args.provider.NewClient(context.Background(), &tt.args.store, tt.args.kube, tt.nameSpace)
- if err != nil && tt.validateErrorNil {
- t.Errorf("ProviderBeyondtrust.NewClient() error = %v", err)
- }
- if err != nil && tt.validateErrorText {
- assert.Equal(t, err.Error(), tt.expectedErrorText)
- }
- })
- }
- }
- func TestLoadConfigSecret_NamespacedStoreCannotCrossNamespace(t *testing.T) {
- kube := fake.NewClientBuilder().WithObjects(&v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: "foo",
- Name: "creds",
- },
- Data: map[string][]byte{
- "key": []byte("value"),
- },
- }).Build()
- ref := &esv1.BeyondTrustProviderSecretRef{
- SecretRef: &esmeta.SecretKeySelector{
- Namespace: new("foo"),
- Name: "creds",
- Key: "key",
- },
- }
- // For a namespaced SecretStore, attempting to read from another namespace must fail.
- _, err := loadConfigSecret(t.Context(), ref, kube, "ns2", esv1.SecretStoreKind)
- if err == nil {
- t.Fatalf("expected error when accessing secret across namespaces with SecretStore, got nil")
- }
- // For a namespaced SecretStore, attempting to read from the right namespace must not fail.
- val, err := loadConfigSecret(t.Context(), ref, kube, "foo", esv1.SecretStoreKind)
- if err != nil {
- t.Fatalf("expected error when accessing secret across namespaces with SecretStore, got nil")
- }
- if val != "value" {
- t.Fatalf("expected value, got %q", val)
- }
- }
- func TestPushSecret(t *testing.T) {
- type testCase struct {
- name string
- serverHandler http.HandlerFunc
- metadata apiextensionsv1.JSON
- expectedError bool
- // apiVersion lets a case target a specific Password Safe API version.
- // Empty string keeps the historical default of 3.1.
- apiVersion string
- }
- tests := []testCase{
- {
- name: "successfully pushes credential secret",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case folderSecretsPath:
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test Credential",
- "username": "admin",
- "description": "Test Credential Secret description",
- "secret_type": "CREDENTIAL",
- "folder_name": "folder1"
- }`),
- },
- },
- {
- name: "successfully pushes file secret",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case "/secrets-safe/folders/cb871861-8b40-4556-820c-1ca6d522adfa/secrets/file":
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test File Secret",
- "username": "admin",
- "description": "Test File Secret description",
- "secret_type": "FILE",
- "folder_name": "folder1",
- "file_name": "credentials.txt"
- }`),
- },
- },
- {
- name: "successfully pushes text secret",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case "/secrets-safe/folders/cb871861-8b40-4556-820c-1ca6d522adfa/secrets/text":
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test Text Secret",
- "username": "admin",
- "description": "Test File Secret description",
- "secret_type": "TEXT",
- "folder_name": "folder1"
- }`),
- },
- },
- {
- name: "successfully pushes credential secret v3.2",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case folderSecretsPath:
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- apiVersion: apiVersion32,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test Credential v3.2",
- "username": "admin",
- "description": "Test Credential Secret description",
- "secret_type": "CREDENTIAL",
- "folder_name": "folder1"
- }`),
- },
- },
- {
- name: "successfully pushes file secret v3.2",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case "/secrets-safe/folders/cb871861-8b40-4556-820c-1ca6d522adfa/secrets/file":
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- apiVersion: apiVersion32,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test File Secret v3.2",
- "username": "admin",
- "description": "Test File Secret description",
- "secret_type": "FILE",
- "folder_name": "folder1",
- "file_name": "credentials.txt"
- }`),
- },
- },
- {
- name: "successfully pushes text secret v3.2",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- case "/secrets-safe/folders/cb871861-8b40-4556-820c-1ca6d522adfa/secrets/text":
- _, err := w.Write([]byte(`{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: false,
- apiVersion: apiVersion32,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test Text Secret v3.2",
- "username": "admin",
- "description": "Test Text Secret description",
- "secret_type": "TEXT",
- "folder_name": "folder1"
- }`),
- },
- },
- {
- name: "successfully pushes text secret - 404 error",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeFoldersPath:
- _, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedError: true,
- metadata: apiextensionsv1.JSON{
- Raw: []byte(`{
- "title": "Test Text Secret",
- "username": "admin",
- "description": "Test File Secret description",
- "secret_type": "TEXT",
- "folder_name": "folder1"
- }`),
- },
- },
- {
- name: "fails authentication",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "unauthorized", http.StatusUnauthorized)
- },
- expectedError: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fakeServer := httptest.NewServer(tt.serverHandler)
- defer fakeServer.Close()
- logger, err := zap.NewDevelopment()
- if err != nil {
- t.Error(err)
- }
- zapLogger := logging.NewZapLogger(logger)
- clientTimeout := 30
- verifyCa := true
- retryMaxElapsedTimeMinutes := 2
- backoffDefinition := backoff.NewExponentialBackOff()
- backoffDefinition.InitialInterval = 1 * time.Second
- backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeMinutes) * time.Second
- backoffDefinition.RandomizationFactor = 0.5
- httpClientObj, err := utils.GetHttpClient(clientTimeout, verifyCa, "", "", zapLogger)
- if err != nil {
- t.Error(err)
- }
- apiVersion := tt.apiVersion
- if apiVersion == "" {
- apiVersion = "3.1"
- }
- params := authentication.AuthenticationParametersObj{
- HTTPClient: *httpClientObj,
- BackoffDefinition: backoffDefinition,
- EndpointURL: fakeServer.URL,
- APIVersion: apiVersion,
- ClientID: "fake_clinet_id",
- ClientSecret: fakeClientSecret,
- Logger: zapLogger,
- RetryMaxElapsedTimeSeconds: 30,
- }
- authObj, err := authentication.Authenticate(params)
- require.NoError(t, err)
- p := &Provider{authenticate: *authObj}
- secret := &v1.Secret{
- Data: map[string][]byte{passwordKey: []byte("supersecret")},
- }
- metadataJSON := &tt.metadata
- psd := v1alpha1.PushSecretData{
- Match: v1alpha1.PushSecretMatch{
- SecretKey: passwordKey,
- RemoteRef: v1alpha1.PushSecretRemoteRef{
- RemoteKey: testCredentialKey,
- },
- },
- Metadata: metadataJSON,
- }
- err = p.PushSecret(context.Background(), secret, psd)
- if tt.expectedError {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- }
- })
- }
- }
- func TestSecretExists(t *testing.T) {
- type testCase struct {
- name string
- serverHandler http.HandlerFunc
- expectedExisting bool
- }
- tests := []testCase{
- {
- name: "Secret Exists",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeSecretsPath:
- _, err := w.Write([]byte(`[{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}]`))
- if err != nil {
- t.Error(err)
- }
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedExisting: true,
- },
- {
- name: "Secret does not Exist",
- serverHandler: func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`))
- if err != nil {
- t.Error(err)
- }
- case authSignAppInPath:
- _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`))
- if err != nil {
- t.Error(err)
- }
- case secretsSafeSecretsPath:
- http.Error(w, "secret was not found", http.StatusNotFound)
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- },
- expectedExisting: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fakeServer := httptest.NewServer(tt.serverHandler)
- defer fakeServer.Close()
- logger, err := zap.NewDevelopment()
- if err != nil {
- t.Error(err)
- }
- zapLogger := logging.NewZapLogger(logger)
- clientTimeout := 30
- verifyCa := true
- retryMaxElapsedTimeMinutes := 2
- backoffDefinition := backoff.NewExponentialBackOff()
- backoffDefinition.InitialInterval = 1 * time.Second
- backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeMinutes) * time.Second
- backoffDefinition.RandomizationFactor = 0.5
- httpClientObj, err := utils.GetHttpClient(clientTimeout, verifyCa, "", "", zapLogger)
- if err != nil {
- t.Error(err)
- }
- params := authentication.AuthenticationParametersObj{
- HTTPClient: *httpClientObj,
- BackoffDefinition: backoffDefinition,
- EndpointURL: fakeServer.URL,
- APIVersion: "3.1",
- ClientID: "fake_clinet_id",
- ClientSecret: fakeClientSecret,
- Logger: zapLogger,
- RetryMaxElapsedTimeSeconds: 30,
- }
- authObj, err := authentication.Authenticate(params)
- require.NoError(t, err)
- p := &Provider{authenticate: *authObj}
- remoteRef := v1alpha1.PushSecretRemoteRef{
- RemoteKey: testCredentialKey,
- }
- exists, err := p.SecretExists(context.Background(), remoteRef)
- if err != nil {
- t.Error(err)
- }
- if tt.expectedExisting {
- assert.True(t, exists)
- } else {
- assert.False(t, exists)
- }
- })
- }
- }
- // writeBody is a small helper for the mock servers below. It fails the test
- // if the response cannot be written, instead of nesting an err-check inside
- // every switch case in the handler.
- func writeBody(t *testing.T, w http.ResponseWriter, body string) {
- t.Helper()
- if _, err := w.Write([]byte(body)); err != nil {
- t.Error(err)
- }
- }
- // newOwnerFieldsMockServer returns an httptest.Server that satisfies the
- // auth + folder lookup + create-secret request flow. The body of the
- // create-secret POST is captured into *captured for assertion.
- func newOwnerFieldsMockServer(t *testing.T, secretsPath string, captured map[string]any) *httptest.Server {
- t.Helper()
- return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.Path {
- case authConnectTokenPath:
- writeBody(t, w, `{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer"}`)
- case authSignAppInPath:
- writeBody(t, w, `{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)
- case secretsSafeFoldersPath:
- writeBody(t, w, `[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}]`)
- case secretsPath:
- bodyBytes, err := io.ReadAll(r.Body)
- require.NoError(t, err)
- require.NoError(t, json.Unmarshal(bodyBytes, &captured))
- writeBody(t, w, `{"Id": "01ca9cf3-0751-4a90-4856-08dcf22d7472","Title": "Secret Title"}`)
- default:
- http.Error(w, "not found", http.StatusNotFound)
- }
- }))
- }
- // newAuthenticatedProvider builds a Provider authenticated against serverURL
- // with the requested API version. Used by tests that need a ready-to-call
- // Provider without inlining the full auth/HTTP-client wiring.
- func newAuthenticatedProvider(t *testing.T, serverURL, apiVersion string) *Provider {
- t.Helper()
- logger, err := zap.NewDevelopment()
- require.NoError(t, err)
- zapLogger := logging.NewZapLogger(logger)
- backoffDefinition := backoff.NewExponentialBackOff()
- backoffDefinition.InitialInterval = 1 * time.Second
- backoffDefinition.MaxElapsedTime = 2 * time.Second
- backoffDefinition.RandomizationFactor = 0.5
- httpClientObj, err := utils.GetHttpClient(30, true, "", "", zapLogger)
- require.NoError(t, err)
- authObj, err := authentication.Authenticate(authentication.AuthenticationParametersObj{
- HTTPClient: *httpClientObj,
- BackoffDefinition: backoffDefinition,
- EndpointURL: serverURL,
- APIVersion: apiVersion,
- ClientID: "fake_client_id",
- ClientSecret: fakeClientSecret,
- Logger: zapLogger,
- RetryMaxElapsedTimeSeconds: 30,
- })
- require.NoError(t, err)
- return &Provider{authenticate: *authObj}
- }
- func ownerFieldsMetadata(secretType string) apiextensionsv1.JSON {
- return apiextensionsv1.JSON{
- Raw: fmt.Appendf(nil, `{
- "title": "Owner Fields Test",
- "username": "admin",
- "secret_type": %q,
- "folder_name": "folder1",
- "owner_type": "User",
- "owner_id": 7,
- "group_id": 42
- }`, secretType),
- }
- }
- // TestPushSecret_OwnerFieldsArePropagated guards the dual-field migration in CreateSecret
- // where every secret-type input carries both OwnersByOwnerId and OwnersByGroupId. The SDK
- // then narrows down to one shape based on API version: v3.0 → OwnerDetailsOwnerId,
- // v3.1/v3.2 → OwnerDetailsGroupId. We assert the request body the SDK actually emitted.
- func TestPushSecret_OwnerFieldsArePropagated(t *testing.T) {
- type testCase struct {
- name string
- apiVersion string
- secretType string
- secretsPath string
- assertBody func(t *testing.T, body map[string]any)
- }
- tests := []testCase{
- {
- name: "v3.0 propagates OwnersByOwnerId and top-level OwnerId/OwnerType for CREDENTIAL",
- apiVersion: "3.0",
- secretType: secretTypeCredential,
- secretsPath: folderSecretsPath,
- assertBody: func(t *testing.T, body map[string]any) {
- assert.Equal(t, float64(7), body["OwnerId"], "top-level OwnerId should come from metadata.owner_id")
- assert.Equal(t, "User", body["OwnerType"], "top-level OwnerType should come from metadata.owner_type")
- owners, ok := body["Owners"].([]any)
- require.True(t, ok, "Owners array missing or wrong type")
- require.NotEmpty(t, owners, "Owners array empty")
- first := owners[0].(map[string]any)
- assert.Equal(t, float64(1), first["OwnerId"], "main owner OwnerId should be UserId=1 from sign-in")
- assert.Equal(t, "test@beyondtrust.com", first["Email"])
- },
- },
- {
- name: "v3.2 propagates OwnersByGroupId for CREDENTIAL",
- apiVersion: apiVersion32,
- secretType: secretTypeCredential,
- secretsPath: folderSecretsPath,
- assertBody: func(t *testing.T, body map[string]any) {
- _, hasOwnerId := body["OwnerId"]
- _, hasOwnerType := body["OwnerType"]
- assert.False(t, hasOwnerId, "v3.2 Config32 should not emit top-level OwnerId")
- assert.False(t, hasOwnerType, "v3.2 Config32 should not emit top-level OwnerType")
- owners, ok := body["Owners"].([]any)
- require.True(t, ok, "Owners array missing or wrong type")
- require.NotEmpty(t, owners, "Owners array empty")
- first := owners[0].(map[string]any)
- assert.Equal(t, float64(42), first["GroupId"], "main owner GroupId should come from metadata.group_id")
- assert.Equal(t, float64(1), first["UserId"], "main owner UserId should be UserId=1 from sign-in")
- assert.Equal(t, "test@beyondtrust.com", first["Email"])
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- capturedBody := make(map[string]any)
- fakeServer := newOwnerFieldsMockServer(t, tt.secretsPath, capturedBody)
- defer fakeServer.Close()
- p := newAuthenticatedProvider(t, fakeServer.URL, tt.apiVersion)
- metadata := ownerFieldsMetadata(tt.secretType)
- psd := v1alpha1.PushSecretData{
- Match: v1alpha1.PushSecretMatch{
- SecretKey: passwordKey,
- RemoteRef: v1alpha1.PushSecretRemoteRef{RemoteKey: testCredentialKey},
- },
- Metadata: &metadata,
- }
- secret := &v1.Secret{Data: map[string][]byte{passwordKey: []byte("supersecret")}}
- require.NoError(t, p.PushSecret(context.Background(), secret, psd))
- require.NotNil(t, capturedBody, "create-secret endpoint was never hit")
- tt.assertBody(t, capturedBody)
- })
- }
- }
|