onepassword_test.go 68 KB

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