onepassword_test.go 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package onepassword
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "reflect"
  20. "testing"
  21. "github.com/1Password/connect-sdk-go/onepassword"
  22. corev1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. pointer "k8s.io/utils/ptr"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  28. "github.com/external-secrets/external-secrets/providers/v1/onepassword/fake"
  29. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  30. )
  31. const (
  32. // vaults and items.
  33. myVault, myVaultID, myVaultUUID = "my-vault", "my-vault-id", "39c31136-d086-47e9-a52c-8fe330d2669a"
  34. myItem, myItemID, myItemUUID = "my-item", "my-item-id", "687adbe7-e6d2-4059-9a62-dbb95d291143"
  35. mySharedVault, mySharedVaultID = "my-shared-vault", "my-shared-vault-id"
  36. mySharedItem, mySharedItemID = "my-shared-item", "my-shared-item-id"
  37. myOtherVault, myOtherVaultID = "my-other-vault", "my-other-vault-id"
  38. myOtherItem, myOtherItemID = "my-other-item", "my-other-item-id"
  39. myNonMatchingVault, myNonMatchingVaultID = "my-non-matching-vault", "my-non-matching-vault-id"
  40. myNonMatchingItem, myNonMatchingItemID = "my-non-matching-item", "my-non-matching-item-id"
  41. // fields and files.
  42. key1, key2, key3, key4 = "key1", "key2", "key3", "key4"
  43. value1, value2, value3, value4 = "value1", "value2", "value3", "value4"
  44. sharedKey1, sharedValue1 = "sharedkey1", "sharedvalue1"
  45. otherKey1 = "otherkey1"
  46. filePNG, filePNGID = "file.png", "file-id"
  47. myFilePNG, myFilePNGID, myContents = "my-file.png", "my-file-id", "my-contents"
  48. mySecondFileTXT, mySecondFileTXTID = "my-second-file.txt", "my-second-file-id"
  49. mySecondContents = "my-second-contents"
  50. myFile2PNG, myFile2TXT = "my-file-2.png", "my-file-2.txt"
  51. myFile2ID, myContents2 = "my-file-2-id", "my-contents-2"
  52. myOtherFilePNG, myOtherFilePNGID = "my-other-file.png", "my-other-file-id"
  53. myOtherContents = "my-other-contents"
  54. nonMatchingFilePNG, nonMatchingFilePNGID = "non-matching-file.png", "non-matching-file-id"
  55. nonMatchingContents = "non-matching-contents"
  56. // other.
  57. mySecret, token, password = "my-secret", "token", "password"
  58. one, two, three = "one", "two", "three"
  59. connectHost = "https://example.com"
  60. setupCheckFormat = "Setup: '%s', Check: '%s'"
  61. getSecretMapErrFormat = "%s: onepassword.GetSecretMap(...): -expected, +got:\n-%#v\n+%#v\n"
  62. getSecretErrFormat = "%s: onepassword.GetSecret(...): -expected, +got:\n-%#v\n+%#v\n"
  63. getAllSecretsErrFormat = "%s: onepassword.GetAllSecrets(...): -expected, +got:\n-%#v\n+%#v\n"
  64. validateStoreErrFormat = "%s: onepassword.validateStore(...): -expected, +got:\n-%#v\n+%#v\n"
  65. findItemErrFormat = "%s: onepassword.findItem(...): -expected, +got:\n-%#v\n+%#v\n"
  66. errFromErrMsgF = "%w: %s"
  67. errDoesNotMatchMsgF = "%s: error did not match: -expected, +got:\\n-%#v\\n+%#v\\n"
  68. )
  69. func TestFindItem(t *testing.T) {
  70. type check struct {
  71. checkNote string
  72. findItemName string
  73. expectedItem *onepassword.Item
  74. expectedErr error
  75. }
  76. type testCase struct {
  77. setupNote string
  78. provider *ProviderOnePassword
  79. checks []check
  80. }
  81. testCases := []testCase{
  82. {
  83. setupNote: "valid basic: one vault, one item, one field",
  84. provider: &ProviderOnePassword{
  85. vaults: map[string]int{myVault: 1},
  86. client: fake.NewMockClient().
  87. AddPredictableVault(myVault).
  88. AddPredictableItemWithField(myVault, myItem, key1, value1),
  89. },
  90. checks: []check{
  91. {
  92. checkNote: "pass",
  93. findItemName: myItem,
  94. expectedErr: nil,
  95. expectedItem: &onepassword.Item{
  96. ID: myItemID,
  97. Title: myItem,
  98. Vault: onepassword.ItemVault{ID: myVaultID},
  99. Fields: []*onepassword.ItemField{
  100. {
  101. Label: key1,
  102. Value: value1,
  103. },
  104. },
  105. },
  106. },
  107. },
  108. },
  109. {
  110. setupNote: "uuid: valid basic: one vault, one item, one field",
  111. provider: &ProviderOnePassword{
  112. vaults: map[string]int{myVaultUUID: 1},
  113. client: fake.NewMockClient().
  114. AddPredictableVaultUUID(myVaultUUID).
  115. AddPredictableItemWithFieldUUID(myVaultUUID, myItemUUID, key1, value1),
  116. },
  117. checks: []check{
  118. {
  119. checkNote: "pass",
  120. findItemName: myItemUUID,
  121. expectedErr: nil,
  122. expectedItem: &onepassword.Item{
  123. ID: myItemUUID,
  124. Title: myItemUUID,
  125. Vault: onepassword.ItemVault{ID: myVaultUUID},
  126. Fields: []*onepassword.ItemField{
  127. {
  128. Label: key1,
  129. Value: value1,
  130. },
  131. },
  132. },
  133. },
  134. },
  135. },
  136. {
  137. setupNote: "multiple vaults, multiple items",
  138. provider: &ProviderOnePassword{
  139. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  140. client: fake.NewMockClient().
  141. AddPredictableVault(myVault).
  142. AddPredictableItemWithField(myVault, myItem, key1, value1).
  143. AddPredictableVault(mySharedVault).
  144. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, sharedValue1),
  145. },
  146. checks: []check{
  147. {
  148. checkNote: "can still get myItem",
  149. findItemName: myItem,
  150. expectedErr: nil,
  151. expectedItem: &onepassword.Item{
  152. ID: myItemID,
  153. Title: myItem,
  154. Vault: onepassword.ItemVault{ID: myVaultID},
  155. Fields: []*onepassword.ItemField{
  156. {
  157. Label: key1,
  158. Value: value1,
  159. },
  160. },
  161. },
  162. },
  163. {
  164. checkNote: "can also get mySharedItem",
  165. findItemName: mySharedItem,
  166. expectedErr: nil,
  167. expectedItem: &onepassword.Item{
  168. ID: mySharedItemID,
  169. Title: mySharedItem,
  170. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  171. Fields: []*onepassword.ItemField{
  172. {
  173. Label: sharedKey1,
  174. Value: sharedValue1,
  175. },
  176. },
  177. },
  178. },
  179. },
  180. },
  181. {
  182. setupNote: "multiple vault matches when should be one",
  183. provider: &ProviderOnePassword{
  184. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  185. client: fake.NewMockClient().
  186. AppendVault(myVault, onepassword.Vault{
  187. ID: myVaultID,
  188. Name: myVault,
  189. }).
  190. AppendVault(myVault, onepassword.Vault{
  191. ID: "my-vault-extra-match-id",
  192. Name: "my-vault-extra-match",
  193. }),
  194. },
  195. checks: []check{
  196. {
  197. checkNote: "two vaults",
  198. findItemName: myItem,
  199. expectedErr: errors.New("key not found in 1Password Vaults: my-item in: map[my-shared-vault:2 my-vault:1]"),
  200. },
  201. },
  202. },
  203. {
  204. setupNote: "no item matches when should be one",
  205. provider: &ProviderOnePassword{
  206. vaults: map[string]int{myVault: 1},
  207. client: fake.NewMockClient().
  208. AddPredictableVault(myVault),
  209. },
  210. checks: []check{
  211. {
  212. checkNote: "no exist",
  213. findItemName: "my-item-no-exist",
  214. expectedErr: fmt.Errorf("%w: my-item-no-exist in: map[my-vault:1]", ErrKeyNotFound),
  215. },
  216. },
  217. },
  218. {
  219. setupNote: "multiple item matches when should be one",
  220. provider: &ProviderOnePassword{
  221. vaults: map[string]int{myVault: 1},
  222. client: fake.NewMockClient().
  223. AddPredictableVault(myVault).
  224. AddPredictableItemWithField(myVault, myItem, key1, value1).
  225. AppendItem(myVaultID, onepassword.Item{
  226. ID: "asdf",
  227. Title: myItem,
  228. Vault: onepassword.ItemVault{ID: myVaultID},
  229. }),
  230. },
  231. checks: []check{
  232. {
  233. checkNote: "multiple match",
  234. findItemName: myItem,
  235. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneItem, "'my-item', got 2"),
  236. },
  237. },
  238. },
  239. {
  240. setupNote: "ordered vaults",
  241. provider: &ProviderOnePassword{
  242. vaults: map[string]int{myVault: 1, mySharedVault: 2, myOtherVault: 3},
  243. client: fake.NewMockClient().
  244. AddPredictableVault(myVault).
  245. AddPredictableVault(mySharedVault).
  246. AddPredictableVault(myOtherVault).
  247. // // my-item
  248. // returned: my-item in my-vault
  249. AddPredictableItemWithField(myVault, myItem, key1, value1).
  250. // preempted: my-item in my-shared-vault
  251. AppendItem(mySharedVaultID, onepassword.Item{
  252. ID: myItemID,
  253. Title: myItem,
  254. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  255. }).
  256. AppendItemField(mySharedVaultID, myItemID, onepassword.ItemField{
  257. Label: key1,
  258. Value: "value1-from-my-shared-vault",
  259. }).
  260. // preempted: my-item in my-other-vault
  261. AppendItem(myOtherVaultID, onepassword.Item{
  262. ID: myItemID,
  263. Title: myItem,
  264. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  265. }).
  266. AppendItemField(myOtherVaultID, myItemID, onepassword.ItemField{
  267. Label: key1,
  268. Value: "value1-from-my-other-vault",
  269. }).
  270. // // my-shared-item
  271. // returned: my-shared-item in my-shared-vault
  272. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, "sharedvalue1-from-my-shared-vault").
  273. // preempted: my-shared-item in my-other-vault
  274. AppendItem(myOtherVaultID, onepassword.Item{
  275. ID: mySharedItemID,
  276. Title: mySharedItem,
  277. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  278. }).
  279. AppendItemField(myOtherVaultID, mySharedItemID, onepassword.ItemField{
  280. Label: sharedKey1,
  281. Value: "sharedvalue1-from-my-other-vault",
  282. }).
  283. // // my-other-item
  284. // returned: my-other-item in my-other-vault
  285. AddPredictableItemWithField(myOtherVault, myOtherItem, otherKey1, "othervalue1-from-my-other-vault"),
  286. },
  287. checks: []check{
  288. {
  289. // my-item in all three vaults, gets the one from my-vault
  290. checkNote: "gets item from my-vault",
  291. findItemName: myItem,
  292. expectedErr: nil,
  293. expectedItem: &onepassword.Item{
  294. ID: myItemID,
  295. Title: myItem,
  296. Vault: onepassword.ItemVault{ID: myVaultID},
  297. Fields: []*onepassword.ItemField{
  298. {
  299. Label: key1,
  300. Value: value1,
  301. },
  302. },
  303. },
  304. },
  305. {
  306. // my-shared-item in my-shared-vault and my-other-vault, gets the one from my-shared-vault
  307. checkNote: "gets item from my-shared-vault",
  308. findItemName: mySharedItem,
  309. expectedErr: nil,
  310. expectedItem: &onepassword.Item{
  311. ID: mySharedItemID,
  312. Title: mySharedItem,
  313. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  314. Fields: []*onepassword.ItemField{
  315. {
  316. Label: sharedKey1,
  317. Value: "sharedvalue1-from-my-shared-vault",
  318. },
  319. },
  320. },
  321. },
  322. {
  323. // my-other-item in my-other-vault
  324. checkNote: "gets item from my-other-vault",
  325. findItemName: myOtherItem,
  326. expectedErr: nil,
  327. expectedItem: &onepassword.Item{
  328. ID: myOtherItemID,
  329. Title: myOtherItem,
  330. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  331. Fields: []*onepassword.ItemField{
  332. {
  333. Label: otherKey1,
  334. Value: "othervalue1-from-my-other-vault",
  335. },
  336. },
  337. },
  338. },
  339. },
  340. },
  341. }
  342. // run the tests
  343. for num, tc := range testCases {
  344. t.Run(fmt.Sprintf("test-%d", num), func(t *testing.T) {
  345. for _, check := range tc.checks {
  346. got, err := tc.provider.findItem(check.findItemName)
  347. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  348. if check.expectedErr == nil && err != nil {
  349. // expected no error, got one
  350. t.Errorf(findItemErrFormat, notes, nil, err)
  351. }
  352. if check.expectedErr != nil && err == nil {
  353. // expected an error, didn't get one
  354. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), nil)
  355. }
  356. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  357. // expected an error, got the wrong one
  358. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), err.Error())
  359. }
  360. if check.expectedItem != nil {
  361. if !reflect.DeepEqual(check.expectedItem, got) {
  362. // expected a predefined item, got something else
  363. t.Errorf(findItemErrFormat, notes, check.expectedItem, got)
  364. }
  365. }
  366. }
  367. })
  368. }
  369. }
  370. func TestValidateStore(t *testing.T) {
  371. type testCase struct {
  372. checkNote string
  373. store *esv1.SecretStore
  374. clusterStore *esv1.ClusterSecretStore
  375. expectedErr error
  376. }
  377. testCases := []testCase{
  378. {
  379. checkNote: "invalid: nil provider",
  380. store: &esv1.SecretStore{
  381. TypeMeta: metav1.TypeMeta{
  382. Kind: "SecretStore",
  383. },
  384. Spec: esv1.SecretStoreSpec{
  385. Provider: nil,
  386. },
  387. },
  388. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProvider)),
  389. },
  390. {
  391. checkNote: "invalid: nil OnePassword provider spec",
  392. store: &esv1.SecretStore{
  393. TypeMeta: metav1.TypeMeta{
  394. Kind: "SecretStore",
  395. },
  396. Spec: esv1.SecretStoreSpec{
  397. Provider: &esv1.SecretStoreProvider{
  398. OnePassword: nil,
  399. },
  400. },
  401. },
  402. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProviderOnePassword)),
  403. },
  404. {
  405. checkNote: "valid secretStore",
  406. store: &esv1.SecretStore{
  407. TypeMeta: metav1.TypeMeta{
  408. Kind: "SecretStore",
  409. },
  410. Spec: esv1.SecretStoreSpec{
  411. Provider: &esv1.SecretStoreProvider{
  412. OnePassword: &esv1.OnePasswordProvider{
  413. Auth: &esv1.OnePasswordAuth{
  414. SecretRef: &esv1.OnePasswordAuthSecretRef{
  415. ConnectToken: esmeta.SecretKeySelector{
  416. Name: mySecret,
  417. Key: token,
  418. },
  419. },
  420. },
  421. ConnectHost: connectHost,
  422. Vaults: map[string]int{
  423. myVault: 1,
  424. },
  425. },
  426. },
  427. },
  428. },
  429. expectedErr: nil,
  430. },
  431. {
  432. checkNote: "invalid: illegal namespace on SecretStore",
  433. store: &esv1.SecretStore{
  434. TypeMeta: metav1.TypeMeta{
  435. Kind: "SecretStore",
  436. },
  437. Spec: esv1.SecretStoreSpec{
  438. Provider: &esv1.SecretStoreProvider{
  439. OnePassword: &esv1.OnePasswordProvider{
  440. Auth: &esv1.OnePasswordAuth{
  441. SecretRef: &esv1.OnePasswordAuthSecretRef{
  442. ConnectToken: esmeta.SecretKeySelector{
  443. Name: mySecret,
  444. Namespace: pointer.To("my-namespace"),
  445. Key: token,
  446. },
  447. },
  448. },
  449. ConnectHost: connectHost,
  450. Vaults: map[string]int{
  451. myVault: 1,
  452. myOtherVault: 2,
  453. },
  454. },
  455. },
  456. },
  457. },
  458. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore")),
  459. },
  460. {
  461. checkNote: "invalid: more than one vault with the same number",
  462. store: &esv1.SecretStore{
  463. TypeMeta: metav1.TypeMeta{
  464. Kind: "SecretStore",
  465. },
  466. Spec: esv1.SecretStoreSpec{
  467. Provider: &esv1.SecretStoreProvider{
  468. OnePassword: &esv1.OnePasswordProvider{
  469. Auth: &esv1.OnePasswordAuth{
  470. SecretRef: &esv1.OnePasswordAuthSecretRef{
  471. ConnectToken: esmeta.SecretKeySelector{
  472. Name: mySecret,
  473. Key: token,
  474. },
  475. },
  476. },
  477. ConnectHost: connectHost,
  478. Vaults: map[string]int{
  479. myVault: 1,
  480. myOtherVault: 1,
  481. },
  482. },
  483. },
  484. },
  485. },
  486. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNonUniqueVaultNumbers)),
  487. },
  488. {
  489. checkNote: "valid: clusterSecretStore",
  490. clusterStore: &esv1.ClusterSecretStore{
  491. TypeMeta: metav1.TypeMeta{
  492. Kind: "ClusterSecretStore",
  493. },
  494. Spec: esv1.SecretStoreSpec{
  495. Provider: &esv1.SecretStoreProvider{
  496. OnePassword: &esv1.OnePasswordProvider{
  497. Auth: &esv1.OnePasswordAuth{
  498. SecretRef: &esv1.OnePasswordAuthSecretRef{
  499. ConnectToken: esmeta.SecretKeySelector{
  500. Name: mySecret,
  501. Namespace: pointer.To("my-namespace"),
  502. Key: token,
  503. },
  504. },
  505. },
  506. ConnectHost: connectHost,
  507. Vaults: map[string]int{
  508. myVault: 1,
  509. },
  510. },
  511. },
  512. },
  513. },
  514. expectedErr: nil,
  515. },
  516. {
  517. checkNote: "invalid: clusterSecretStore without namespace",
  518. clusterStore: &esv1.ClusterSecretStore{
  519. TypeMeta: metav1.TypeMeta{
  520. Kind: "ClusterSecretStore",
  521. },
  522. Spec: esv1.SecretStoreSpec{
  523. Provider: &esv1.SecretStoreProvider{
  524. OnePassword: &esv1.OnePasswordProvider{
  525. Auth: &esv1.OnePasswordAuth{
  526. SecretRef: &esv1.OnePasswordAuthSecretRef{
  527. ConnectToken: esmeta.SecretKeySelector{
  528. Name: mySecret,
  529. Key: token,
  530. },
  531. },
  532. },
  533. ConnectHost: connectHost,
  534. Vaults: map[string]int{
  535. myVault: 1,
  536. myOtherVault: 2,
  537. },
  538. },
  539. },
  540. },
  541. },
  542. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("cluster scope requires namespace")),
  543. },
  544. {
  545. checkNote: "invalid: missing connectTokenSecretRef.name",
  546. store: &esv1.SecretStore{
  547. TypeMeta: metav1.TypeMeta{
  548. Kind: "SecretStore",
  549. },
  550. Spec: esv1.SecretStoreSpec{
  551. Provider: &esv1.SecretStoreProvider{
  552. OnePassword: &esv1.OnePasswordProvider{
  553. Auth: &esv1.OnePasswordAuth{
  554. SecretRef: &esv1.OnePasswordAuthSecretRef{
  555. ConnectToken: esmeta.SecretKeySelector{
  556. Key: token,
  557. },
  558. },
  559. },
  560. ConnectHost: connectHost,
  561. Vaults: map[string]int{
  562. myVault: 1,
  563. myOtherVault: 2,
  564. },
  565. },
  566. },
  567. },
  568. },
  569. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefName)),
  570. },
  571. {
  572. checkNote: "invalid: missing connectTokenSecretRef.key",
  573. store: &esv1.SecretStore{
  574. TypeMeta: metav1.TypeMeta{
  575. Kind: "SecretStore",
  576. },
  577. Spec: esv1.SecretStoreSpec{
  578. Provider: &esv1.SecretStoreProvider{
  579. OnePassword: &esv1.OnePasswordProvider{
  580. Auth: &esv1.OnePasswordAuth{
  581. SecretRef: &esv1.OnePasswordAuthSecretRef{
  582. ConnectToken: esmeta.SecretKeySelector{
  583. Name: mySecret,
  584. },
  585. },
  586. },
  587. ConnectHost: connectHost,
  588. Vaults: map[string]int{
  589. myVault: 1,
  590. myOtherVault: 2,
  591. },
  592. },
  593. },
  594. },
  595. },
  596. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefKey)),
  597. },
  598. {
  599. checkNote: "invalid: at least one vault",
  600. store: &esv1.SecretStore{
  601. TypeMeta: metav1.TypeMeta{
  602. Kind: "SecretStore",
  603. },
  604. Spec: esv1.SecretStoreSpec{
  605. Provider: &esv1.SecretStoreProvider{
  606. OnePassword: &esv1.OnePasswordProvider{
  607. Auth: &esv1.OnePasswordAuth{
  608. SecretRef: &esv1.OnePasswordAuthSecretRef{
  609. ConnectToken: esmeta.SecretKeySelector{
  610. Name: mySecret,
  611. Key: token,
  612. },
  613. },
  614. },
  615. ConnectHost: connectHost,
  616. Vaults: map[string]int{},
  617. },
  618. },
  619. },
  620. },
  621. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreAtLeastOneVault)),
  622. },
  623. {
  624. checkNote: "invalid: url",
  625. store: &esv1.SecretStore{
  626. TypeMeta: metav1.TypeMeta{
  627. Kind: "SecretStore",
  628. },
  629. Spec: esv1.SecretStoreSpec{
  630. Provider: &esv1.SecretStoreProvider{
  631. OnePassword: &esv1.OnePasswordProvider{
  632. Auth: &esv1.OnePasswordAuth{
  633. SecretRef: &esv1.OnePasswordAuthSecretRef{
  634. ConnectToken: esmeta.SecretKeySelector{
  635. Name: mySecret,
  636. Key: token,
  637. },
  638. },
  639. },
  640. ConnectHost: ":/invalid.invalid",
  641. Vaults: map[string]int{
  642. myVault: 1,
  643. },
  644. },
  645. },
  646. },
  647. },
  648. expectedErr: fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreInvalidConnectHost, errors.New("parse \":/invalid.invalid\": missing protocol scheme"))),
  649. },
  650. }
  651. // run the tests
  652. for _, tc := range testCases {
  653. var err error
  654. if tc.store == nil {
  655. err = validateStore(tc.clusterStore)
  656. } else {
  657. err = validateStore(tc.store)
  658. }
  659. notes := fmt.Sprintf("Check: '%s'", tc.checkNote)
  660. if tc.expectedErr == nil && err != nil {
  661. // expected no error, got one
  662. t.Errorf(validateStoreErrFormat, notes, nil, err)
  663. }
  664. if tc.expectedErr != nil && err == nil {
  665. // expected an error, didn't get one
  666. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), nil)
  667. }
  668. if tc.expectedErr != nil && err != nil && err.Error() != tc.expectedErr.Error() {
  669. // expected an error, got the wrong one
  670. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), err.Error())
  671. }
  672. }
  673. }
  674. // most functionality is tested in TestFindItem
  675. //
  676. // here we just check that an empty Property defaults to "password",
  677. // files are loaded, and
  678. // the data or errors are properly returned
  679. func TestGetSecret(t *testing.T) {
  680. type check struct {
  681. checkNote string
  682. ref esv1.ExternalSecretDataRemoteRef
  683. expectedValue string
  684. expectedErr error
  685. }
  686. type testCase struct {
  687. setupNote string
  688. provider *ProviderOnePassword
  689. checks []check
  690. }
  691. testCases := []testCase{
  692. {
  693. setupNote: "one vault, one item, two fields",
  694. provider: &ProviderOnePassword{
  695. vaults: map[string]int{myVault: 1},
  696. client: fake.NewMockClient().
  697. AddPredictableVault(myVault).
  698. AppendItem(myVaultID, onepassword.Item{
  699. ID: myItemID,
  700. Title: myItem,
  701. Vault: onepassword.ItemVault{ID: myVaultID},
  702. Files: []*onepassword.File{
  703. {
  704. ID: myFilePNGID,
  705. Name: myFilePNG,
  706. },
  707. },
  708. }).
  709. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  710. Label: password,
  711. Value: value2,
  712. }).
  713. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  714. Label: key1,
  715. Value: value1,
  716. }).
  717. SetFileContents(myFilePNG, []byte(myContents)),
  718. },
  719. checks: []check{
  720. {
  721. checkNote: key1,
  722. ref: esv1.ExternalSecretDataRemoteRef{
  723. Key: myItem,
  724. Property: key1,
  725. },
  726. expectedValue: value1,
  727. expectedErr: nil,
  728. },
  729. {
  730. checkNote: key1 + " with prefix",
  731. ref: esv1.ExternalSecretDataRemoteRef{
  732. Key: myItem,
  733. Property: fieldPrefix + prefixSplitter + key1,
  734. },
  735. expectedValue: value1,
  736. expectedErr: nil,
  737. },
  738. {
  739. checkNote: "'password' (defaulted property)",
  740. ref: esv1.ExternalSecretDataRemoteRef{
  741. Key: myItem,
  742. },
  743. expectedValue: value2,
  744. expectedErr: nil,
  745. },
  746. {
  747. checkNote: "'ref.version' not implemented",
  748. ref: esv1.ExternalSecretDataRemoteRef{
  749. Key: myItem,
  750. Property: key1,
  751. Version: "123",
  752. },
  753. expectedErr: errors.New(errVersionNotImplemented),
  754. },
  755. {
  756. checkNote: "file named my-file.png with prefix",
  757. ref: esv1.ExternalSecretDataRemoteRef{
  758. Key: myItem,
  759. Property: filePrefix + prefixSplitter + myFilePNG,
  760. },
  761. expectedValue: myContents,
  762. expectedErr: nil,
  763. },
  764. },
  765. },
  766. {
  767. setupNote: "files are loaded",
  768. provider: &ProviderOnePassword{
  769. vaults: map[string]int{myVault: 1},
  770. client: fake.NewMockClient().
  771. AddPredictableVault(myVault).
  772. AppendItem(myVaultID, onepassword.Item{
  773. ID: myItemID,
  774. Title: myItem,
  775. Vault: onepassword.ItemVault{ID: myVaultID},
  776. Category: documentCategory,
  777. Files: []*onepassword.File{
  778. {
  779. ID: myFilePNGID,
  780. Name: myFilePNG,
  781. },
  782. },
  783. }).
  784. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  785. Label: key1,
  786. Value: value2,
  787. }).
  788. SetFileContents(myFilePNG, []byte(myContents)),
  789. },
  790. checks: []check{
  791. {
  792. checkNote: "field named password",
  793. ref: esv1.ExternalSecretDataRemoteRef{
  794. Key: myItem,
  795. Property: fieldPrefix + prefixSplitter + key1,
  796. },
  797. expectedValue: value2,
  798. expectedErr: nil,
  799. },
  800. {
  801. checkNote: "file named my-file.png",
  802. ref: esv1.ExternalSecretDataRemoteRef{
  803. Key: myItem,
  804. Property: myFilePNG,
  805. },
  806. expectedValue: myContents,
  807. expectedErr: nil,
  808. },
  809. {
  810. checkNote: "file named my-file.png with prefix",
  811. ref: esv1.ExternalSecretDataRemoteRef{
  812. Key: myItem,
  813. Property: filePrefix + prefixSplitter + myFilePNG,
  814. },
  815. expectedValue: myContents,
  816. expectedErr: nil,
  817. },
  818. {
  819. checkNote: "empty ref.Property",
  820. ref: esv1.ExternalSecretDataRemoteRef{
  821. Key: myItem,
  822. },
  823. expectedValue: myContents,
  824. expectedErr: nil,
  825. },
  826. {
  827. checkNote: "file non existent",
  828. ref: esv1.ExternalSecretDataRemoteRef{
  829. Key: myItem,
  830. Property: "you-cant-find-me.png",
  831. },
  832. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  833. },
  834. {
  835. checkNote: "file non existent with prefix",
  836. ref: esv1.ExternalSecretDataRemoteRef{
  837. Key: myItem,
  838. Property: "file/you-cant-find-me.png",
  839. },
  840. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  841. },
  842. },
  843. },
  844. {
  845. setupNote: "one vault, one item, two fields w/ same Label",
  846. provider: &ProviderOnePassword{
  847. vaults: map[string]int{myVault: 1},
  848. client: fake.NewMockClient().
  849. AddPredictableVault(myVault).
  850. AddPredictableItemWithField(myVault, myItem, key1, value1).
  851. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  852. Label: key1,
  853. Value: value2,
  854. }),
  855. },
  856. checks: []check{
  857. {
  858. checkNote: key1,
  859. ref: esv1.ExternalSecretDataRemoteRef{
  860. Key: myItem,
  861. Property: key1,
  862. },
  863. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  864. },
  865. },
  866. },
  867. }
  868. // run the tests
  869. for _, tc := range testCases {
  870. for _, check := range tc.checks {
  871. got, err := tc.provider.GetSecret(context.Background(), check.ref)
  872. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  873. if check.expectedErr == nil && err != nil {
  874. // expected no error, got one
  875. t.Errorf(getSecretErrFormat, notes, nil, err)
  876. }
  877. if check.expectedErr != nil && err == nil {
  878. // expected an error, didn't get one
  879. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), nil)
  880. }
  881. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  882. // expected an error, got the wrong one
  883. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), err.Error())
  884. }
  885. if check.expectedValue != "" {
  886. if check.expectedValue != string(got) {
  887. // expected a predefined value, got something else
  888. t.Errorf(getSecretErrFormat, notes, check.expectedValue, string(got))
  889. }
  890. }
  891. }
  892. }
  893. }
  894. // most functionality is tested in TestFindItem. here we just check:
  895. //
  896. // all keys are fetched and the map is compiled correctly,
  897. // files are loaded, and the data or errors are properly returned.
  898. func TestGetSecretMap(t *testing.T) {
  899. type check struct {
  900. checkNote string
  901. ref esv1.ExternalSecretDataRemoteRef
  902. expectedMap map[string][]byte
  903. expectedErr error
  904. }
  905. type testCase struct {
  906. setupNote string
  907. provider *ProviderOnePassword
  908. checks []check
  909. }
  910. testCases := []testCase{
  911. {
  912. setupNote: "one vault, one item, two fields",
  913. provider: &ProviderOnePassword{
  914. vaults: map[string]int{myVault: 1},
  915. client: fake.NewMockClient().
  916. AddPredictableVault(myVault).
  917. AppendItem(myVaultID, onepassword.Item{
  918. ID: myItemID,
  919. Title: myItem,
  920. Vault: onepassword.ItemVault{ID: myVaultID},
  921. Files: []*onepassword.File{
  922. {
  923. ID: myFilePNGID,
  924. Name: myFilePNG,
  925. },
  926. {
  927. ID: myFile2ID,
  928. Name: myFile2PNG,
  929. },
  930. },
  931. }).
  932. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  933. Label: key1,
  934. Value: value1,
  935. }).
  936. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  937. Label: password,
  938. Value: value2,
  939. }).
  940. SetFileContents(myFilePNG, []byte(myContents)).
  941. SetFileContents(myFile2PNG, []byte(myContents2)),
  942. },
  943. checks: []check{
  944. {
  945. checkNote: "all Properties",
  946. ref: esv1.ExternalSecretDataRemoteRef{
  947. Key: myItem,
  948. },
  949. expectedMap: map[string][]byte{
  950. key1: []byte(value1),
  951. password: []byte(value2),
  952. },
  953. expectedErr: nil,
  954. },
  955. {
  956. checkNote: "limit by Property",
  957. ref: esv1.ExternalSecretDataRemoteRef{
  958. Key: myItem,
  959. Property: password,
  960. },
  961. expectedMap: map[string][]byte{
  962. password: []byte(value2),
  963. },
  964. expectedErr: nil,
  965. },
  966. {
  967. checkNote: "'ref.version' not implemented",
  968. ref: esv1.ExternalSecretDataRemoteRef{
  969. Key: myItem,
  970. Property: key1,
  971. Version: "123",
  972. },
  973. expectedErr: errors.New(errVersionNotImplemented),
  974. },
  975. {
  976. checkNote: "limit by Property with prefix",
  977. ref: esv1.ExternalSecretDataRemoteRef{
  978. Key: myItem,
  979. Property: filePrefix + prefixSplitter + myFilePNG,
  980. },
  981. expectedMap: map[string][]byte{
  982. myFilePNG: []byte(myContents),
  983. },
  984. expectedErr: nil,
  985. },
  986. },
  987. },
  988. {
  989. setupNote: "files",
  990. provider: &ProviderOnePassword{
  991. vaults: map[string]int{myVault: 1},
  992. client: fake.NewMockClient().
  993. AddPredictableVault(myVault).
  994. AppendItem(myVaultID, onepassword.Item{
  995. ID: myItemID,
  996. Title: myItem,
  997. Vault: onepassword.ItemVault{ID: myVaultID},
  998. Category: documentCategory,
  999. Files: []*onepassword.File{
  1000. {
  1001. ID: myFilePNGID,
  1002. Name: myFilePNG,
  1003. },
  1004. {
  1005. ID: myFile2ID,
  1006. Name: myFile2PNG,
  1007. },
  1008. },
  1009. }).
  1010. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1011. Label: key1,
  1012. Value: value2,
  1013. }).
  1014. SetFileContents(myFilePNG, []byte(myContents)).
  1015. SetFileContents(myFile2PNG, []byte(myContents2)),
  1016. },
  1017. checks: []check{
  1018. {
  1019. checkNote: "all Properties",
  1020. ref: esv1.ExternalSecretDataRemoteRef{
  1021. Key: myItem,
  1022. },
  1023. expectedMap: map[string][]byte{
  1024. myFilePNG: []byte(myContents),
  1025. myFile2PNG: []byte(myContents2),
  1026. },
  1027. expectedErr: nil,
  1028. },
  1029. {
  1030. checkNote: "limit by Property",
  1031. ref: esv1.ExternalSecretDataRemoteRef{
  1032. Key: myItem,
  1033. Property: myFilePNG,
  1034. },
  1035. expectedMap: map[string][]byte{
  1036. myFilePNG: []byte(myContents),
  1037. },
  1038. expectedErr: nil,
  1039. },
  1040. {
  1041. checkNote: "limit by Property with prefix",
  1042. ref: esv1.ExternalSecretDataRemoteRef{
  1043. Key: myItem,
  1044. Property: filePrefix + prefixSplitter + myFilePNG,
  1045. },
  1046. expectedMap: map[string][]byte{
  1047. myFilePNG: []byte(myContents),
  1048. },
  1049. expectedErr: nil,
  1050. },
  1051. {
  1052. checkNote: "get field limit by Property",
  1053. ref: esv1.ExternalSecretDataRemoteRef{
  1054. Key: myItem,
  1055. Property: fieldPrefix + prefixSplitter + key1,
  1056. },
  1057. expectedMap: map[string][]byte{
  1058. key1: []byte(value2),
  1059. },
  1060. expectedErr: nil,
  1061. },
  1062. },
  1063. },
  1064. {
  1065. setupNote: "one vault, one item, two fields w/ same Label",
  1066. provider: &ProviderOnePassword{
  1067. vaults: map[string]int{myVault: 1},
  1068. client: fake.NewMockClient().
  1069. AddPredictableVault(myVault).
  1070. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1071. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1072. Label: key1,
  1073. Value: value2,
  1074. }),
  1075. },
  1076. checks: []check{
  1077. {
  1078. checkNote: key1,
  1079. ref: esv1.ExternalSecretDataRemoteRef{
  1080. Key: myItem,
  1081. },
  1082. expectedMap: nil,
  1083. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  1084. },
  1085. },
  1086. },
  1087. }
  1088. // run the tests
  1089. for _, tc := range testCases {
  1090. for _, check := range tc.checks {
  1091. gotMap, err := tc.provider.GetSecretMap(context.Background(), check.ref)
  1092. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1093. if check.expectedErr == nil && err != nil {
  1094. // expected no error, got one
  1095. t.Errorf(getSecretMapErrFormat, notes, nil, err)
  1096. }
  1097. if check.expectedErr != nil && err == nil {
  1098. // expected an error, didn't get one
  1099. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), nil)
  1100. }
  1101. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1102. // expected an error, got the wrong one
  1103. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), err.Error())
  1104. }
  1105. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1106. // expected a predefined map, got something else
  1107. t.Errorf(getSecretMapErrFormat, notes, check.expectedMap, gotMap)
  1108. }
  1109. }
  1110. }
  1111. }
  1112. func TestGetAllSecrets(t *testing.T) {
  1113. type check struct {
  1114. checkNote string
  1115. ref esv1.ExternalSecretFind
  1116. expectedMap map[string][]byte
  1117. expectedErr error
  1118. }
  1119. type testCase struct {
  1120. setupNote string
  1121. provider *ProviderOnePassword
  1122. checks []check
  1123. }
  1124. testCases := []testCase{
  1125. {
  1126. setupNote: "three vaults, three items, all different field Labels",
  1127. provider: &ProviderOnePassword{
  1128. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1129. client: fake.NewMockClient().
  1130. AddPredictableVault(myVault).
  1131. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1132. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1133. Label: key2,
  1134. Value: value2,
  1135. }).
  1136. AddPredictableVault(myOtherVault).
  1137. AddPredictableItemWithField(myOtherVault, myOtherItem, key3, value3).
  1138. AppendItemField(myOtherVaultID, myOtherItemID, onepassword.ItemField{
  1139. Label: key4,
  1140. Value: value4,
  1141. }).
  1142. AddPredictableVault(myNonMatchingVault).
  1143. AddPredictableItemWithField(myNonMatchingVault, myNonMatchingItem, "non-matching5", "value5").
  1144. AppendItemField(myNonMatchingVaultID, myNonMatchingItemID, onepassword.ItemField{
  1145. Label: "non-matching6",
  1146. Value: "value6",
  1147. }),
  1148. },
  1149. checks: []check{
  1150. {
  1151. checkNote: "find some with path only",
  1152. ref: esv1.ExternalSecretFind{
  1153. Path: pointer.To(myItem),
  1154. },
  1155. expectedMap: map[string][]byte{
  1156. key1: []byte(value1),
  1157. key2: []byte(value2),
  1158. },
  1159. expectedErr: nil,
  1160. },
  1161. {
  1162. checkNote: "find most with regex 'key*'",
  1163. ref: esv1.ExternalSecretFind{
  1164. Name: &esv1.FindName{
  1165. RegExp: "key*",
  1166. },
  1167. },
  1168. expectedMap: map[string][]byte{
  1169. key1: []byte(value1),
  1170. key2: []byte(value2),
  1171. key3: []byte(value3),
  1172. key4: []byte(value4),
  1173. },
  1174. expectedErr: nil,
  1175. },
  1176. {
  1177. checkNote: "find some with regex 'key*' and path 'my-other-item'",
  1178. ref: esv1.ExternalSecretFind{
  1179. Name: &esv1.FindName{
  1180. RegExp: "key*",
  1181. },
  1182. Path: pointer.To(myOtherItem),
  1183. },
  1184. expectedMap: map[string][]byte{
  1185. key3: []byte(value3),
  1186. key4: []byte(value4),
  1187. },
  1188. expectedErr: nil,
  1189. },
  1190. {
  1191. checkNote: "find none with regex 'asdf*'",
  1192. ref: esv1.ExternalSecretFind{
  1193. Name: &esv1.FindName{
  1194. RegExp: "asdf*",
  1195. },
  1196. },
  1197. expectedMap: map[string][]byte{},
  1198. expectedErr: nil,
  1199. },
  1200. {
  1201. checkNote: "find none with path 'no-exist'",
  1202. ref: esv1.ExternalSecretFind{
  1203. Name: &esv1.FindName{
  1204. RegExp: "key*",
  1205. },
  1206. Path: pointer.To("no-exist"),
  1207. },
  1208. expectedMap: map[string][]byte{},
  1209. expectedErr: nil,
  1210. },
  1211. },
  1212. },
  1213. {
  1214. setupNote: "one vault, three items, find by tags",
  1215. provider: &ProviderOnePassword{
  1216. vaults: map[string]int{myVault: 1},
  1217. client: fake.NewMockClient().
  1218. AddPredictableVault(myVault).
  1219. AppendItem(myVaultID, onepassword.Item{
  1220. ID: myItemID,
  1221. Title: myItem,
  1222. Tags: []string{"foo", "bar"},
  1223. Vault: onepassword.ItemVault{ID: myVaultID},
  1224. }).
  1225. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1226. Label: key1,
  1227. Value: value1,
  1228. }).
  1229. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1230. Label: key2,
  1231. Value: value2,
  1232. }).
  1233. AppendItem(myVaultID, onepassword.Item{
  1234. ID: "my-item-id-2",
  1235. Title: "my-item-2",
  1236. Vault: onepassword.ItemVault{ID: myVaultID},
  1237. Tags: []string{"foo", "baz"},
  1238. }).
  1239. AppendItemField(myVaultID, "my-item-id-2", onepassword.ItemField{
  1240. Label: key3,
  1241. Value: value3,
  1242. }).
  1243. AppendItem(myVaultID, onepassword.Item{
  1244. ID: "my-item-id-3",
  1245. Title: "my-item-3",
  1246. Vault: onepassword.ItemVault{ID: myVaultID},
  1247. Tags: []string{"bang", "bing"},
  1248. }).
  1249. AppendItemField(myVaultID, "my-item-id-3", onepassword.ItemField{
  1250. Label: key4,
  1251. Value: value4,
  1252. }),
  1253. },
  1254. checks: []check{
  1255. {
  1256. checkNote: "find with tags",
  1257. ref: esv1.ExternalSecretFind{
  1258. Path: pointer.To(myItem),
  1259. Tags: map[string]string{
  1260. "foo": "true",
  1261. "bar": "true",
  1262. },
  1263. },
  1264. expectedMap: map[string][]byte{
  1265. key1: []byte(value1),
  1266. key2: []byte(value2),
  1267. },
  1268. expectedErr: nil,
  1269. },
  1270. {
  1271. checkNote: "find with tags and get all",
  1272. ref: esv1.ExternalSecretFind{
  1273. Path: pointer.To(myItem),
  1274. Tags: map[string]string{
  1275. "foo": "true",
  1276. },
  1277. },
  1278. expectedMap: map[string][]byte{
  1279. key1: []byte(value1),
  1280. key2: []byte(value2),
  1281. key3: []byte(value3),
  1282. },
  1283. expectedErr: nil,
  1284. },
  1285. },
  1286. },
  1287. {
  1288. setupNote: "3 vaults, 4 items, 5 files",
  1289. provider: &ProviderOnePassword{
  1290. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1291. client: fake.NewMockClient().
  1292. // my-vault
  1293. AddPredictableVault(myVault).
  1294. AppendItem(myVaultID, onepassword.Item{
  1295. ID: myItemID,
  1296. Title: myItem,
  1297. Vault: onepassword.ItemVault{ID: myVaultID},
  1298. Category: documentCategory,
  1299. Files: []*onepassword.File{
  1300. {
  1301. ID: myFilePNGID,
  1302. Name: myFilePNG,
  1303. },
  1304. {
  1305. ID: mySecondFileTXTID,
  1306. Name: mySecondFileTXT,
  1307. },
  1308. },
  1309. }).
  1310. SetFileContents(myFilePNG, []byte(myContents)).
  1311. SetFileContents(mySecondFileTXT, []byte(mySecondContents)).
  1312. AppendItem(myVaultID, onepassword.Item{
  1313. ID: "my-item-2-id",
  1314. Title: "my-item-2",
  1315. Vault: onepassword.ItemVault{ID: myVaultID},
  1316. Category: documentCategory,
  1317. Files: []*onepassword.File{
  1318. {
  1319. ID: myFile2ID,
  1320. Name: myFile2TXT,
  1321. },
  1322. },
  1323. }).
  1324. SetFileContents(myFile2TXT, []byte(myContents2)).
  1325. // my-other-vault
  1326. AddPredictableVault(myOtherVault).
  1327. AppendItem(myOtherVaultID, onepassword.Item{
  1328. ID: myOtherItemID,
  1329. Title: myOtherItem,
  1330. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1331. Category: documentCategory,
  1332. Files: []*onepassword.File{
  1333. {
  1334. ID: myOtherFilePNGID,
  1335. Name: myOtherFilePNG,
  1336. },
  1337. },
  1338. }).
  1339. SetFileContents(myOtherFilePNG, []byte(myOtherContents)).
  1340. // my-non-matching-vault
  1341. AddPredictableVault(myNonMatchingVault).
  1342. AppendItem(myNonMatchingVaultID, onepassword.Item{
  1343. ID: myNonMatchingItemID,
  1344. Title: myNonMatchingItem,
  1345. Vault: onepassword.ItemVault{ID: myNonMatchingVaultID},
  1346. Category: documentCategory,
  1347. Files: []*onepassword.File{
  1348. {
  1349. ID: nonMatchingFilePNGID,
  1350. Name: nonMatchingFilePNG,
  1351. },
  1352. },
  1353. }).
  1354. SetFileContents(nonMatchingFilePNG, []byte(nonMatchingContents)),
  1355. },
  1356. checks: []check{
  1357. {
  1358. checkNote: "find most with regex '^my-*'",
  1359. ref: esv1.ExternalSecretFind{
  1360. Name: &esv1.FindName{
  1361. RegExp: "^my-*",
  1362. },
  1363. },
  1364. expectedMap: map[string][]byte{
  1365. myFilePNG: []byte(myContents),
  1366. mySecondFileTXT: []byte(mySecondContents),
  1367. myFile2TXT: []byte(myContents2),
  1368. myOtherFilePNG: []byte(myOtherContents),
  1369. },
  1370. expectedErr: nil,
  1371. },
  1372. {
  1373. checkNote: "find some with regex '^my-*' and path 'my-other-item'",
  1374. ref: esv1.ExternalSecretFind{
  1375. Name: &esv1.FindName{
  1376. RegExp: "^my-*",
  1377. },
  1378. Path: pointer.To(myOtherItem),
  1379. },
  1380. expectedMap: map[string][]byte{
  1381. myOtherFilePNG: []byte(myOtherContents),
  1382. },
  1383. expectedErr: nil,
  1384. },
  1385. {
  1386. checkNote: "find none with regex '^asdf*'",
  1387. ref: esv1.ExternalSecretFind{
  1388. Name: &esv1.FindName{
  1389. RegExp: "^asdf*",
  1390. },
  1391. },
  1392. expectedMap: map[string][]byte{},
  1393. expectedErr: nil,
  1394. },
  1395. {
  1396. checkNote: "find none with path 'no-exist'",
  1397. ref: esv1.ExternalSecretFind{
  1398. Name: &esv1.FindName{
  1399. RegExp: "^my-*",
  1400. },
  1401. Path: pointer.To("no-exist"),
  1402. },
  1403. expectedMap: map[string][]byte{},
  1404. expectedErr: nil,
  1405. },
  1406. },
  1407. },
  1408. {
  1409. setupNote: "two fields/files with same name, first one wins",
  1410. provider: &ProviderOnePassword{
  1411. vaults: map[string]int{myVault: 1, myOtherVault: 2},
  1412. client: fake.NewMockClient().
  1413. // my-vault
  1414. AddPredictableVault(myVault).
  1415. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1416. AddPredictableItemWithField(myVault, "my-second-item", key1, "value-second").
  1417. AppendItem(myVaultID, onepassword.Item{
  1418. ID: "file-item-id",
  1419. Title: "file-item",
  1420. Vault: onepassword.ItemVault{ID: myVaultID},
  1421. Category: documentCategory,
  1422. Files: []*onepassword.File{
  1423. {
  1424. ID: filePNGID,
  1425. Name: filePNG,
  1426. },
  1427. },
  1428. }).
  1429. SetFileContents(filePNG, []byte(myContents)).
  1430. AppendItem(myVaultID, onepassword.Item{
  1431. ID: "file-item-2-id",
  1432. Title: "file-item-2",
  1433. Vault: onepassword.ItemVault{ID: myVaultID},
  1434. Category: documentCategory,
  1435. Files: []*onepassword.File{
  1436. {
  1437. ID: "file-2-id",
  1438. Name: filePNG,
  1439. },
  1440. },
  1441. }).
  1442. // my-other-vault
  1443. AddPredictableVault(myOtherVault).
  1444. AddPredictableItemWithField(myOtherVault, myOtherItem, key1, "value-other").
  1445. AppendItem(myOtherVaultID, onepassword.Item{
  1446. ID: "file-item-other-id",
  1447. Title: "file-item-other",
  1448. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1449. Category: documentCategory,
  1450. Files: []*onepassword.File{
  1451. {
  1452. ID: "other-file-id",
  1453. Name: filePNG,
  1454. },
  1455. },
  1456. }),
  1457. },
  1458. checks: []check{
  1459. {
  1460. checkNote: "find fields with regex '^key*'",
  1461. ref: esv1.ExternalSecretFind{
  1462. Name: &esv1.FindName{
  1463. RegExp: "^key*",
  1464. },
  1465. },
  1466. expectedMap: map[string][]byte{
  1467. key1: []byte(value1),
  1468. },
  1469. expectedErr: nil,
  1470. },
  1471. {
  1472. checkNote: "find files with regex '^file*item*'",
  1473. ref: esv1.ExternalSecretFind{
  1474. Name: &esv1.FindName{
  1475. RegExp: "^file*",
  1476. },
  1477. },
  1478. expectedMap: map[string][]byte{
  1479. filePNG: []byte(myContents),
  1480. },
  1481. expectedErr: nil,
  1482. },
  1483. },
  1484. },
  1485. }
  1486. // run the tests
  1487. for _, tc := range testCases {
  1488. for _, check := range tc.checks {
  1489. gotMap, err := tc.provider.GetAllSecrets(context.Background(), check.ref)
  1490. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1491. if check.expectedErr == nil && err != nil {
  1492. // expected no error, got one
  1493. t.Fatalf(getAllSecretsErrFormat, notes, nil, err)
  1494. }
  1495. if check.expectedErr != nil && err == nil {
  1496. // expected an error, didn't get one
  1497. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), nil)
  1498. }
  1499. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1500. // expected an error, got the wrong one
  1501. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), err.Error())
  1502. }
  1503. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1504. // expected a predefined map, got something else
  1505. t.Errorf(getAllSecretsErrFormat, notes, check.expectedMap, gotMap)
  1506. }
  1507. }
  1508. }
  1509. }
  1510. func TestSortVaults(t *testing.T) {
  1511. type testCase struct {
  1512. vaults map[string]int
  1513. expected []string
  1514. }
  1515. testCases := []testCase{
  1516. {
  1517. vaults: map[string]int{
  1518. one: 1,
  1519. three: 3,
  1520. two: 2,
  1521. },
  1522. expected: []string{
  1523. one,
  1524. two,
  1525. three,
  1526. },
  1527. },
  1528. {
  1529. vaults: map[string]int{
  1530. "four": 100,
  1531. one: 1,
  1532. three: 3,
  1533. two: 2,
  1534. },
  1535. expected: []string{
  1536. one,
  1537. two,
  1538. three,
  1539. "four",
  1540. },
  1541. },
  1542. }
  1543. // run the tests
  1544. for _, tc := range testCases {
  1545. got := sortVaults(tc.vaults)
  1546. if !reflect.DeepEqual(got, tc.expected) {
  1547. t.Errorf("onepassword.sortVaults(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1548. }
  1549. }
  1550. }
  1551. func TestHasUniqueVaultNumbers(t *testing.T) {
  1552. type testCase struct {
  1553. vaults map[string]int
  1554. expected bool
  1555. }
  1556. testCases := []testCase{
  1557. {
  1558. vaults: map[string]int{
  1559. one: 1,
  1560. three: 3,
  1561. two: 2,
  1562. },
  1563. expected: true,
  1564. },
  1565. {
  1566. vaults: map[string]int{
  1567. "four": 100,
  1568. one: 1,
  1569. three: 3,
  1570. two: 2,
  1571. "eight": 100,
  1572. },
  1573. expected: false,
  1574. },
  1575. {
  1576. vaults: map[string]int{
  1577. one: 1,
  1578. "1": 1,
  1579. three: 3,
  1580. two: 2,
  1581. },
  1582. expected: false,
  1583. },
  1584. }
  1585. // run the tests
  1586. for _, tc := range testCases {
  1587. got := hasUniqueVaultNumbers(tc.vaults)
  1588. if got != tc.expected {
  1589. t.Errorf("onepassword.hasUniqueVaultNumbers(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1590. }
  1591. }
  1592. }
  1593. type fakeRef struct {
  1594. key string
  1595. prop string
  1596. secretKey string
  1597. metadata *apiextensionsv1.JSON
  1598. }
  1599. func (f fakeRef) GetRemoteKey() string {
  1600. return f.key
  1601. }
  1602. func (f fakeRef) GetProperty() string {
  1603. return f.prop
  1604. }
  1605. func (f fakeRef) GetSecretKey() string {
  1606. return f.secretKey
  1607. }
  1608. func (f fakeRef) GetMetadata() *apiextensionsv1.JSON {
  1609. return f.metadata
  1610. }
  1611. func validateItem(t *testing.T, expectedItem, actualItem *onepassword.Item) {
  1612. t.Helper()
  1613. if !reflect.DeepEqual(expectedItem, actualItem) {
  1614. t.Errorf("expected item %v, got %v", expectedItem, actualItem)
  1615. }
  1616. }
  1617. func TestProviderOnePasswordCreateItem(t *testing.T) {
  1618. type testCase struct {
  1619. vaults map[string]int
  1620. expectedErr error
  1621. setupNote string
  1622. val []byte
  1623. createValidateFunc func(*testing.T, *onepassword.Item, string) (*onepassword.Item, error)
  1624. ref esv1.PushSecretData
  1625. }
  1626. const vaultName = "vault1"
  1627. const fallbackVaultName = "vault2"
  1628. thridPartyErr := errors.New("third party error")
  1629. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  1630. APIVersion: metadata.APIVersion,
  1631. Kind: metadata.Kind,
  1632. Spec: PushSecretMetadataSpec{
  1633. Tags: []string{"tag1", "tag2"},
  1634. Vault: fallbackVaultName,
  1635. },
  1636. }
  1637. metadataRaw, _ := json.Marshal(metadata)
  1638. testCases := []testCase{
  1639. {
  1640. setupNote: "standard create",
  1641. val: []byte("value"),
  1642. ref: fakeRef{
  1643. key: "testing",
  1644. prop: "prop",
  1645. },
  1646. expectedErr: nil,
  1647. vaults: map[string]int{
  1648. vaultName: 1,
  1649. fallbackVaultName: 2,
  1650. },
  1651. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1652. validateItem(t, &onepassword.Item{
  1653. Title: "testing",
  1654. Category: onepassword.Server,
  1655. Vault: onepassword.ItemVault{
  1656. ID: vaultName,
  1657. },
  1658. Fields: []*onepassword.ItemField{
  1659. generateNewItemField("prop", "value"),
  1660. },
  1661. }, item)
  1662. return item, nil
  1663. },
  1664. },
  1665. {
  1666. setupNote: "standard create with no property",
  1667. val: []byte("value2"),
  1668. ref: fakeRef{
  1669. key: "testing2",
  1670. prop: "",
  1671. },
  1672. vaults: map[string]int{
  1673. vaultName: 2,
  1674. },
  1675. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1676. validateItem(t, &onepassword.Item{
  1677. Title: "testing2",
  1678. Category: onepassword.Server,
  1679. Vault: onepassword.ItemVault{
  1680. ID: vaultName,
  1681. },
  1682. Fields: []*onepassword.ItemField{
  1683. generateNewItemField("password", "value2"),
  1684. },
  1685. }, item)
  1686. return item, nil
  1687. },
  1688. },
  1689. {
  1690. setupNote: "no vaults",
  1691. val: []byte("value"),
  1692. ref: fakeRef{
  1693. key: "testing",
  1694. prop: "prop",
  1695. },
  1696. vaults: map[string]int{},
  1697. expectedErr: ErrNoVaults,
  1698. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1699. t.Errorf("onepassword.createItem(...): should not have been called")
  1700. return nil, nil
  1701. },
  1702. },
  1703. {
  1704. setupNote: "error on create",
  1705. val: []byte("testing"),
  1706. ref: fakeRef{
  1707. key: "another",
  1708. prop: "property",
  1709. },
  1710. vaults: map[string]int{
  1711. vaultName: 1,
  1712. },
  1713. expectedErr: thridPartyErr,
  1714. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1715. validateItem(t, &onepassword.Item{
  1716. Title: "another",
  1717. Category: onepassword.Server,
  1718. Vault: onepassword.ItemVault{
  1719. ID: vaultName,
  1720. },
  1721. Fields: []*onepassword.ItemField{
  1722. generateNewItemField("property", "testing"),
  1723. },
  1724. }, item)
  1725. return nil, thridPartyErr
  1726. },
  1727. },
  1728. {
  1729. setupNote: "valid metadata overrides",
  1730. val: []byte("testing"),
  1731. ref: fakeRef{
  1732. key: "another",
  1733. prop: "property",
  1734. metadata: &apiextensionsv1.JSON{
  1735. Raw: metadataRaw,
  1736. },
  1737. },
  1738. vaults: map[string]int{
  1739. vaultName: 1,
  1740. fallbackVaultName: 2,
  1741. },
  1742. expectedErr: nil,
  1743. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1744. validateItem(t, &onepassword.Item{
  1745. Title: "another",
  1746. Category: onepassword.Server,
  1747. Vault: onepassword.ItemVault{
  1748. ID: fallbackVaultName,
  1749. },
  1750. Fields: []*onepassword.ItemField{
  1751. generateNewItemField("property", "testing"),
  1752. },
  1753. Tags: []string{"tag1", "tag2"},
  1754. }, item)
  1755. return item, nil
  1756. },
  1757. },
  1758. }
  1759. provider := &ProviderOnePassword{}
  1760. for _, tc := range testCases {
  1761. // setup
  1762. mockClient := fake.NewMockClient()
  1763. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  1764. i, e := tc.createValidateFunc(t, item, s)
  1765. return i, e
  1766. }
  1767. provider.client = mockClient
  1768. provider.vaults = tc.vaults
  1769. err := provider.createItem(tc.val, tc.ref)
  1770. if !errors.Is(err, tc.expectedErr) {
  1771. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  1772. }
  1773. }
  1774. }
  1775. func TestProviderOnePasswordDeleteItem(t *testing.T) {
  1776. type testCase struct {
  1777. inputFields []*onepassword.ItemField
  1778. fieldName string
  1779. expectedErr error
  1780. expectedFields []*onepassword.ItemField
  1781. setupNote string
  1782. }
  1783. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  1784. testCases := []testCase{
  1785. {
  1786. setupNote: "one field to remove",
  1787. inputFields: []*onepassword.ItemField{
  1788. {
  1789. ID: field1,
  1790. Label: field1,
  1791. Type: onepassword.FieldTypeAddress,
  1792. },
  1793. {
  1794. ID: field2,
  1795. Label: field2,
  1796. Type: onepassword.FieldTypeString,
  1797. },
  1798. {
  1799. ID: field3,
  1800. Label: field3,
  1801. Type: onepassword.FieldTypeConcealed,
  1802. },
  1803. },
  1804. fieldName: field2,
  1805. expectedFields: []*onepassword.ItemField{
  1806. {
  1807. ID: field1,
  1808. Label: field1,
  1809. Type: onepassword.FieldTypeAddress,
  1810. },
  1811. {
  1812. ID: field3,
  1813. Label: field3,
  1814. Type: onepassword.FieldTypeConcealed,
  1815. },
  1816. },
  1817. },
  1818. {
  1819. setupNote: "no fields to remove",
  1820. inputFields: []*onepassword.ItemField{
  1821. {
  1822. ID: field1,
  1823. Label: field1,
  1824. Type: onepassword.FieldTypeAddress,
  1825. },
  1826. {
  1827. ID: field2,
  1828. Label: field2,
  1829. Type: onepassword.FieldTypeString,
  1830. },
  1831. {
  1832. ID: field3,
  1833. Label: field3,
  1834. Type: onepassword.FieldTypeConcealed,
  1835. },
  1836. },
  1837. expectedErr: nil,
  1838. fieldName: field4,
  1839. expectedFields: []*onepassword.ItemField{
  1840. {
  1841. ID: field1,
  1842. Label: field1,
  1843. Type: onepassword.FieldTypeAddress,
  1844. },
  1845. {
  1846. ID: field2,
  1847. Label: field2,
  1848. Type: onepassword.FieldTypeString,
  1849. },
  1850. {
  1851. ID: field3,
  1852. Label: field3,
  1853. Type: onepassword.FieldTypeConcealed,
  1854. },
  1855. },
  1856. },
  1857. {
  1858. setupNote: "multiple fields to remove",
  1859. inputFields: []*onepassword.ItemField{
  1860. {
  1861. ID: field3,
  1862. Label: field3,
  1863. Type: onepassword.FieldTypeConcealed,
  1864. },
  1865. {
  1866. ID: field1,
  1867. Label: field1,
  1868. Type: onepassword.FieldTypeAddress,
  1869. },
  1870. {
  1871. ID: field3,
  1872. Label: field3,
  1873. Type: onepassword.FieldTypeCreditCardType,
  1874. },
  1875. {
  1876. ID: field2,
  1877. Label: field2,
  1878. Type: onepassword.FieldTypeString,
  1879. },
  1880. {
  1881. ID: field3,
  1882. Label: field3,
  1883. Type: onepassword.FieldTypeGender,
  1884. },
  1885. },
  1886. fieldName: field3,
  1887. expectedErr: ErrExpectedOneField,
  1888. expectedFields: nil,
  1889. },
  1890. }
  1891. // run the tests
  1892. for _, tc := range testCases {
  1893. actualOutput, err := deleteField(tc.inputFields, tc.fieldName)
  1894. if len(actualOutput) != len(tc.expectedFields) {
  1895. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  1896. return
  1897. }
  1898. if !errors.Is(err, tc.expectedErr) {
  1899. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  1900. }
  1901. for i, check := range tc.expectedFields {
  1902. if len(actualOutput) <= i {
  1903. continue
  1904. }
  1905. if !reflect.DeepEqual(check, actualOutput[i]) {
  1906. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  1907. }
  1908. }
  1909. }
  1910. }
  1911. func TestUpdateFields(t *testing.T) {
  1912. type testCase struct {
  1913. inputFields []*onepassword.ItemField
  1914. fieldName string
  1915. newVal string
  1916. expectedErr error
  1917. expectedFields []*onepassword.ItemField
  1918. setupNote string
  1919. }
  1920. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  1921. testCases := []testCase{
  1922. {
  1923. setupNote: "one field to update",
  1924. inputFields: []*onepassword.ItemField{
  1925. {
  1926. ID: field1,
  1927. Label: field1,
  1928. Value: value1,
  1929. Type: onepassword.FieldTypeAddress,
  1930. },
  1931. {
  1932. ID: field2,
  1933. Label: field2,
  1934. Value: value2,
  1935. Type: onepassword.FieldTypeString,
  1936. },
  1937. {
  1938. ID: field3,
  1939. Label: field3,
  1940. Value: value3,
  1941. Type: onepassword.FieldTypeConcealed,
  1942. },
  1943. },
  1944. fieldName: field2,
  1945. newVal: "testing",
  1946. expectedFields: []*onepassword.ItemField{
  1947. {
  1948. ID: field1,
  1949. Label: field1,
  1950. Value: value1,
  1951. Type: onepassword.FieldTypeAddress,
  1952. },
  1953. {
  1954. ID: field2,
  1955. Label: field2,
  1956. Value: "testing",
  1957. Type: onepassword.FieldTypeString,
  1958. },
  1959. {
  1960. ID: field3,
  1961. Label: field3,
  1962. Value: value3,
  1963. Type: onepassword.FieldTypeConcealed,
  1964. },
  1965. },
  1966. },
  1967. {
  1968. setupNote: "add field",
  1969. inputFields: []*onepassword.ItemField{
  1970. {
  1971. ID: field1,
  1972. Value: value1,
  1973. Label: field1,
  1974. Type: onepassword.FieldTypeAddress,
  1975. },
  1976. {
  1977. ID: field2,
  1978. Label: field2,
  1979. Value: value2,
  1980. Type: onepassword.FieldTypeString,
  1981. },
  1982. },
  1983. fieldName: field4,
  1984. newVal: value4,
  1985. expectedFields: []*onepassword.ItemField{
  1986. {
  1987. ID: field1,
  1988. Label: field1,
  1989. Value: value1,
  1990. Type: onepassword.FieldTypeAddress,
  1991. },
  1992. {
  1993. ID: field2,
  1994. Label: field2,
  1995. Value: value2,
  1996. Type: onepassword.FieldTypeString,
  1997. },
  1998. {
  1999. Label: field4,
  2000. Value: value4,
  2001. Type: onepassword.FieldTypeConcealed,
  2002. },
  2003. },
  2004. },
  2005. {
  2006. setupNote: "no changes",
  2007. inputFields: []*onepassword.ItemField{
  2008. {
  2009. ID: field1,
  2010. Label: field1,
  2011. Value: value1,
  2012. Type: onepassword.FieldTypeAddress,
  2013. },
  2014. {
  2015. ID: field2,
  2016. Label: field2,
  2017. Value: value2,
  2018. Type: onepassword.FieldTypeString,
  2019. },
  2020. },
  2021. fieldName: field1,
  2022. newVal: value1,
  2023. expectedErr: nil,
  2024. expectedFields: []*onepassword.ItemField{
  2025. {
  2026. ID: field1,
  2027. Label: field1,
  2028. Value: value1,
  2029. Type: onepassword.FieldTypeAddress,
  2030. },
  2031. {
  2032. ID: field2,
  2033. Label: field2,
  2034. Value: value2,
  2035. Type: onepassword.FieldTypeString,
  2036. },
  2037. },
  2038. },
  2039. {
  2040. setupNote: "multiple fields to remove",
  2041. inputFields: []*onepassword.ItemField{
  2042. {
  2043. ID: field3,
  2044. Label: field3,
  2045. Value: value3,
  2046. Type: onepassword.FieldTypeConcealed,
  2047. },
  2048. {
  2049. ID: field1,
  2050. Label: field1,
  2051. Value: value1,
  2052. Type: onepassword.FieldTypeAddress,
  2053. },
  2054. {
  2055. ID: field3,
  2056. Label: field3,
  2057. Value: value3,
  2058. Type: onepassword.FieldTypeCreditCardType,
  2059. },
  2060. {
  2061. ID: field2,
  2062. Label: field2,
  2063. Value: value2,
  2064. Type: onepassword.FieldTypeString,
  2065. },
  2066. {
  2067. ID: field3,
  2068. Label: field3,
  2069. Value: value3,
  2070. Type: onepassword.FieldTypeGender,
  2071. },
  2072. },
  2073. fieldName: field3,
  2074. expectedErr: ErrExpectedOneField,
  2075. expectedFields: nil,
  2076. },
  2077. }
  2078. // run the tests
  2079. for _, tc := range testCases {
  2080. actualOutput, err := updateFieldValue(tc.inputFields, tc.fieldName, tc.newVal)
  2081. if len(actualOutput) != len(tc.expectedFields) {
  2082. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  2083. return
  2084. }
  2085. if !errors.Is(err, tc.expectedErr) {
  2086. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2087. }
  2088. for i, check := range tc.expectedFields {
  2089. if len(actualOutput) <= i {
  2090. continue
  2091. }
  2092. if !reflect.DeepEqual(check, actualOutput[i]) {
  2093. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  2094. }
  2095. }
  2096. }
  2097. }
  2098. func TestGenerateNewItemField(t *testing.T) {
  2099. field := generateNewItemField("property", "testing")
  2100. if !reflect.DeepEqual(field, &onepassword.ItemField{
  2101. Label: "property",
  2102. Type: onepassword.FieldTypeConcealed,
  2103. Value: "testing",
  2104. }) {
  2105. t.Errorf("field did not match: -expected, +got:\n-%#v\n+%#v\n", &onepassword.ItemField{
  2106. Label: "property",
  2107. Type: onepassword.FieldTypeConcealed,
  2108. Value: "testing",
  2109. }, field)
  2110. }
  2111. }
  2112. func TestProviderOnePasswordPushSecret(t *testing.T) {
  2113. // Most logic is tested in the createItem and updateField functions
  2114. // This test is just to make sure the correct functions are called.
  2115. // the correct values are passed to them, and errors are propagated
  2116. type testCase struct {
  2117. vaults map[string]int
  2118. expectedErr error
  2119. setupNote string
  2120. existingItems []onepassword.Item
  2121. val *corev1.Secret
  2122. existingItemsFields map[string][]*onepassword.ItemField
  2123. createValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2124. updateValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2125. ref fakeRef
  2126. }
  2127. var (
  2128. vaultName = "vault1"
  2129. vault = onepassword.Vault{
  2130. ID: vaultName,
  2131. }
  2132. )
  2133. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  2134. APIVersion: metadata.APIVersion,
  2135. Kind: metadata.Kind,
  2136. Spec: PushSecretMetadataSpec{
  2137. Tags: []string{"tag1", "tag2"},
  2138. },
  2139. }
  2140. metadataRaw, _ := json.Marshal(metadata)
  2141. testCases := []testCase{
  2142. {
  2143. vaults: map[string]int{
  2144. vaultName: 1,
  2145. },
  2146. expectedErr: ErrExpectedOneItem,
  2147. setupNote: "find item error",
  2148. existingItems: []onepassword.Item{
  2149. {
  2150. Title: key1,
  2151. }, {
  2152. Title: key1,
  2153. }, // can be empty, testing for error with length
  2154. },
  2155. ref: fakeRef{
  2156. key: key1,
  2157. secretKey: key1,
  2158. },
  2159. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2160. },
  2161. {
  2162. setupNote: "create item error",
  2163. expectedErr: ErrNoVaults,
  2164. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2165. ref: fakeRef{secretKey: key1},
  2166. vaults: nil,
  2167. },
  2168. {
  2169. setupNote: "key not in data",
  2170. expectedErr: ErrKeyNotFound,
  2171. val: &corev1.Secret{Data: map[string][]byte{}},
  2172. ref: fakeRef{secretKey: key1},
  2173. vaults: nil,
  2174. },
  2175. {
  2176. setupNote: "create item success",
  2177. expectedErr: nil,
  2178. val: &corev1.Secret{Data: map[string][]byte{
  2179. key1: []byte("testing"),
  2180. }},
  2181. ref: fakeRef{
  2182. key: key1,
  2183. prop: "prop",
  2184. secretKey: key1,
  2185. },
  2186. vaults: map[string]int{
  2187. vaultName: 1,
  2188. },
  2189. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2190. validateItem(t, &onepassword.Item{
  2191. Title: key1,
  2192. Category: onepassword.Server,
  2193. Vault: onepassword.ItemVault{
  2194. ID: vaultName,
  2195. },
  2196. Fields: []*onepassword.ItemField{
  2197. generateNewItemField("prop", "testing"),
  2198. },
  2199. }, item)
  2200. return item, nil
  2201. },
  2202. },
  2203. {
  2204. setupNote: "update fields error",
  2205. expectedErr: ErrExpectedOneField,
  2206. val: &corev1.Secret{Data: map[string][]byte{
  2207. "key2": []byte("testing"),
  2208. }},
  2209. ref: fakeRef{
  2210. key: key1,
  2211. prop: "prop",
  2212. secretKey: "key2",
  2213. },
  2214. vaults: map[string]int{
  2215. vaultName: 1,
  2216. },
  2217. existingItemsFields: map[string][]*onepassword.ItemField{
  2218. key1: {
  2219. {
  2220. Label: "prop",
  2221. },
  2222. {
  2223. Label: "prop",
  2224. },
  2225. },
  2226. },
  2227. existingItems: []onepassword.Item{
  2228. {
  2229. Vault: onepassword.ItemVault{
  2230. ID: vaultName,
  2231. },
  2232. ID: key1,
  2233. Title: key1,
  2234. },
  2235. },
  2236. },
  2237. {
  2238. setupNote: "standard update",
  2239. expectedErr: nil,
  2240. val: &corev1.Secret{Data: map[string][]byte{
  2241. "key3": []byte("testing2"),
  2242. }},
  2243. ref: fakeRef{
  2244. key: key1,
  2245. prop: "",
  2246. secretKey: "key3",
  2247. },
  2248. vaults: map[string]int{
  2249. vaultName: 1,
  2250. },
  2251. existingItemsFields: map[string][]*onepassword.ItemField{
  2252. key1: {
  2253. {
  2254. Label: "not-prop",
  2255. },
  2256. },
  2257. },
  2258. updateValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2259. expectedItem := &onepassword.Item{
  2260. Vault: onepassword.ItemVault{
  2261. ID: vaultName,
  2262. },
  2263. ID: key1,
  2264. Title: key1,
  2265. Fields: []*onepassword.ItemField{
  2266. {
  2267. Label: "not-prop",
  2268. },
  2269. {
  2270. Label: "password",
  2271. Value: "testing2",
  2272. Type: onepassword.FieldTypeConcealed,
  2273. },
  2274. },
  2275. }
  2276. validateItem(t, expectedItem, item)
  2277. return expectedItem, nil
  2278. },
  2279. existingItems: []onepassword.Item{
  2280. {
  2281. Vault: onepassword.ItemVault{
  2282. ID: vaultName,
  2283. },
  2284. ID: key1,
  2285. Title: key1,
  2286. },
  2287. },
  2288. },
  2289. {
  2290. setupNote: "create item with metadata overwrites success",
  2291. expectedErr: nil,
  2292. val: &corev1.Secret{Data: map[string][]byte{
  2293. key1: []byte("testing"),
  2294. }},
  2295. ref: fakeRef{
  2296. key: key1,
  2297. prop: "prop",
  2298. secretKey: key1,
  2299. metadata: &apiextensionsv1.JSON{
  2300. Raw: metadataRaw,
  2301. },
  2302. },
  2303. vaults: map[string]int{
  2304. vaultName: 1,
  2305. },
  2306. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2307. validateItem(t, &onepassword.Item{
  2308. Title: key1,
  2309. Category: onepassword.Server,
  2310. Vault: onepassword.ItemVault{
  2311. ID: vaultName,
  2312. },
  2313. Fields: []*onepassword.ItemField{
  2314. generateNewItemField("prop", "testing"),
  2315. },
  2316. Tags: []string{"tag1", "tag2"},
  2317. }, item)
  2318. return item, nil
  2319. },
  2320. },
  2321. }
  2322. provider := &ProviderOnePassword{}
  2323. for _, tc := range testCases {
  2324. t.Run(tc.setupNote, func(t *testing.T) {
  2325. // setup
  2326. mockClient := fake.NewMockClient()
  2327. mockClient.MockVaults = map[string][]onepassword.Vault{
  2328. vaultName: {vault},
  2329. }
  2330. mockClient.MockItems = map[string][]onepassword.Item{
  2331. vaultName: tc.existingItems,
  2332. }
  2333. mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
  2334. vaultName: tc.existingItemsFields,
  2335. }
  2336. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2337. return tc.createValidateFunc(item, s)
  2338. }
  2339. mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2340. return tc.updateValidateFunc(item, s)
  2341. }
  2342. provider.client = mockClient
  2343. provider.vaults = tc.vaults
  2344. err := provider.PushSecret(context.Background(), tc.val, tc.ref)
  2345. if !errors.Is(err, tc.expectedErr) {
  2346. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2347. }
  2348. })
  2349. }
  2350. }
  2351. // mockClient implements connect.Client interface for testing.
  2352. type mockClient struct {
  2353. getItemsFunc func(vaultQuery string) ([]onepassword.Item, error)
  2354. }
  2355. func (m *mockClient) GetVaults() ([]onepassword.Vault, error) { return nil, nil }
  2356. func (m *mockClient) GetVault(uuid string) (*onepassword.Vault, error) { return nil, nil }
  2357. func (m *mockClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) { return nil, nil }
  2358. func (m *mockClient) GetVaultByTitle(title string) (*onepassword.Vault, error) { return nil, nil }
  2359. func (m *mockClient) GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) { return nil, nil }
  2360. func (m *mockClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
  2361. if m.getItemsFunc != nil {
  2362. return m.getItemsFunc(vaultQuery)
  2363. }
  2364. return nil, nil
  2365. }
  2366. func (m *mockClient) GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error) {
  2367. return nil, nil
  2368. }
  2369. func (m *mockClient) GetItemByUUID(uuid, vaultQuery string) (*onepassword.Item, error) {
  2370. return nil, nil
  2371. }
  2372. func (m *mockClient) GetItemByTitle(title, vaultQuery string) (*onepassword.Item, error) {
  2373. return nil, nil
  2374. }
  2375. func (m *mockClient) GetItemsByTitle(title, vaultQuery string) ([]onepassword.Item, error) {
  2376. return nil, nil
  2377. }
  2378. func (m *mockClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2379. return nil, nil
  2380. }
  2381. func (m *mockClient) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2382. return nil, nil
  2383. }
  2384. func (m *mockClient) DeleteItem(item *onepassword.Item, vaultQuery string) error { return nil }
  2385. func (m *mockClient) DeleteItemByID(itemUUID, vaultQuery string) error { return nil }
  2386. func (m *mockClient) DeleteItemByTitle(title, vaultQuery string) error { return nil }
  2387. func (m *mockClient) GetFiles(itemQuery, vaultQuery string) ([]onepassword.File, error) {
  2388. return nil, nil
  2389. }
  2390. func (m *mockClient) GetFile(uuid, itemQuery, vaultQuery string) (*onepassword.File, error) {
  2391. return nil, nil
  2392. }
  2393. func (m *mockClient) GetFileContent(file *onepassword.File) ([]byte, error) { return nil, nil }
  2394. func (m *mockClient) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
  2395. return "", nil
  2396. }
  2397. func (m *mockClient) LoadStructFromItemByUUID(config interface{}, itemUUID, vaultQuery string) error {
  2398. return nil
  2399. }
  2400. func (m *mockClient) LoadStructFromItemByTitle(config interface{}, itemTitle, vaultQuery string) error {
  2401. return nil
  2402. }
  2403. func (m *mockClient) LoadStructFromItem(config interface{}, itemQuery, vaultQuery string) error {
  2404. return nil
  2405. }
  2406. func (m *mockClient) LoadStruct(config interface{}) error { return nil }
  2407. func TestRetryClient(t *testing.T) {
  2408. tests := []struct {
  2409. name string
  2410. err error
  2411. shouldRetry bool
  2412. expectErr bool
  2413. }{
  2414. {
  2415. name: "403 auth error should retry",
  2416. err: errors.New("status 403: Authorization failed"),
  2417. shouldRetry: true,
  2418. expectErr: true,
  2419. },
  2420. {
  2421. name: "other error should not retry",
  2422. err: errors.New("status 500: Internal Server Error"),
  2423. shouldRetry: false,
  2424. expectErr: true,
  2425. },
  2426. {
  2427. name: "nil error should not retry",
  2428. err: nil,
  2429. shouldRetry: false,
  2430. expectErr: false,
  2431. },
  2432. }
  2433. for _, tc := range tests {
  2434. t.Run(tc.name, func(t *testing.T) {
  2435. callCount := 0
  2436. mockClient := &mockClient{
  2437. getItemsFunc: func(vaultQuery string) ([]onepassword.Item, error) {
  2438. callCount++
  2439. return nil, tc.err
  2440. },
  2441. }
  2442. retryClient := newRetryClient(mockClient)
  2443. _, err := retryClient.GetItems("test-vault")
  2444. if tc.expectErr && err == nil {
  2445. t.Errorf("expected error but got none")
  2446. }
  2447. if !tc.expectErr && err != nil {
  2448. t.Errorf("expected no error but got: %v", err)
  2449. }
  2450. expectedCalls := 1
  2451. if tc.shouldRetry {
  2452. expectedCalls = 3 // Initial call + 2 retries (3 steps configured in retry backoff)
  2453. }
  2454. if callCount < expectedCalls {
  2455. t.Errorf("expected at least %d calls but got %d", expectedCalls, callCount)
  2456. }
  2457. })
  2458. }
  2459. }
  2460. func TestIs403AuthError(t *testing.T) {
  2461. tests := []struct {
  2462. name string
  2463. err error
  2464. expected bool
  2465. }{
  2466. {
  2467. name: "nil error",
  2468. err: nil,
  2469. expected: false,
  2470. },
  2471. {
  2472. name: "403 auth error",
  2473. err: errors.New("status 403: Authorization failed"),
  2474. expected: true,
  2475. },
  2476. {
  2477. name: "other error",
  2478. err: errors.New("status 500: Internal Server Error"),
  2479. expected: false,
  2480. },
  2481. {
  2482. name: "partial match",
  2483. err: errors.New("403: some other message"),
  2484. expected: false,
  2485. },
  2486. }
  2487. for _, tc := range tests {
  2488. t.Run(tc.name, func(t *testing.T) {
  2489. result := is403AuthError(tc.err)
  2490. if result != tc.expected {
  2491. t.Errorf("expected %v but got %v", tc.expected, result)
  2492. }
  2493. })
  2494. }
  2495. }