client_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. Copyright © The ESO Authors
  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 barbican
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "net/http"
  19. "testing"
  20. "github.com/gophercloud/gophercloud/v2"
  21. th "github.com/gophercloud/gophercloud/v2/testhelper"
  22. thclient "github.com/gophercloud/gophercloud/v2/testhelper/client"
  23. "github.com/stretchr/testify/assert"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. )
  26. func TestExtractUUIDFromRef(t *testing.T) {
  27. testCases := []struct {
  28. name string
  29. secretRef string
  30. expectedUUID string
  31. }{
  32. {
  33. name: "valid barbican secret ref",
  34. secretRef: "https://barbican.example.com/v1/secrets/12345678-1234-1234-1234-123456789abc",
  35. expectedUUID: "12345678-1234-1234-1234-123456789abc",
  36. },
  37. {
  38. name: "secret ref without protocol",
  39. secretRef: "barbican.example.com/v1/secrets/87654321-4321-4321-4321-cba987654321",
  40. expectedUUID: "87654321-4321-4321-4321-cba987654321",
  41. },
  42. {
  43. name: "empty string",
  44. secretRef: "",
  45. expectedUUID: "",
  46. },
  47. {
  48. name: "trailing slash",
  49. secretRef: "https://barbican.example.com/v1/secrets/12345678-1234-1234-1234-123456789abc/",
  50. expectedUUID: "",
  51. },
  52. }
  53. for _, tc := range testCases {
  54. t.Run(tc.name, func(t *testing.T) {
  55. uuid := extractUUIDFromRef(tc.secretRef)
  56. assert.Equal(t, tc.expectedUUID, uuid)
  57. })
  58. }
  59. }
  60. func TestGetSecretPayloadProperty(t *testing.T) {
  61. testPayload := []byte(`{"username":"admin","password":"secret123","nested":{"key":"value"}}`)
  62. testCases := []struct {
  63. name string
  64. payload []byte
  65. property string
  66. expectError bool
  67. errorMessage string
  68. expectedData []byte
  69. }{
  70. {
  71. name: "empty property returns full payload",
  72. payload: testPayload,
  73. property: "",
  74. expectError: false,
  75. expectedData: testPayload,
  76. },
  77. {
  78. name: "valid property extraction",
  79. payload: testPayload,
  80. property: "username",
  81. expectError: false,
  82. expectedData: []byte(`"admin"`),
  83. },
  84. {
  85. name: "nested property extraction",
  86. payload: testPayload,
  87. property: "nested",
  88. expectError: false,
  89. expectedData: []byte(`{"key":"value"}`),
  90. },
  91. {
  92. name: "property not found",
  93. payload: testPayload,
  94. property: "nonexistent",
  95. expectError: true,
  96. errorMessage: "property nonexistent not found in secret payload",
  97. },
  98. {
  99. name: "invalid JSON",
  100. payload: []byte("invalid-json"),
  101. property: "username",
  102. expectError: true,
  103. errorMessage: "barbican client",
  104. },
  105. }
  106. for _, tc := range testCases {
  107. t.Run(tc.name, func(t *testing.T) {
  108. data, err := getSecretPayloadProperty(tc.payload, tc.property)
  109. if tc.expectError {
  110. assert.Error(t, err)
  111. assert.Contains(t, err.Error(), tc.errorMessage)
  112. assert.Nil(t, data)
  113. } else {
  114. assert.NoError(t, err)
  115. assert.Equal(t, tc.expectedData, data)
  116. }
  117. })
  118. }
  119. }
  120. func TestUnsupportedOperations(t *testing.T) {
  121. client := &Client{
  122. keyManager: &gophercloud.ServiceClient{},
  123. }
  124. // Test PushSecret
  125. err := client.PushSecret(context.Background(), nil, nil)
  126. assert.Error(t, err)
  127. assert.Contains(t, err.Error(), "does not support pushing secrets")
  128. // Test SecretExists
  129. exists, err := client.SecretExists(context.Background(), nil)
  130. assert.Error(t, err)
  131. assert.False(t, exists)
  132. assert.Contains(t, err.Error(), "does not support checking secret existence")
  133. // Test DeleteSecret
  134. err = client.DeleteSecret(context.Background(), nil)
  135. assert.Error(t, err)
  136. assert.Contains(t, err.Error(), "does not support deleting secrets")
  137. }
  138. func TestValidateAndClose(t *testing.T) {
  139. client := &Client{
  140. keyManager: &gophercloud.ServiceClient{},
  141. }
  142. // Test Validate
  143. result, err := client.Validate()
  144. assert.NoError(t, err)
  145. assert.Equal(t, esv1.ValidationResultUnknown, result)
  146. // Test Close
  147. err = client.Close(context.Background())
  148. assert.NoError(t, err)
  149. }
  150. func TestGetAllSecretsValidation(t *testing.T) {
  151. client := &Client{
  152. keyManager: &gophercloud.ServiceClient{},
  153. }
  154. testCases := []struct {
  155. name string
  156. findRef esv1.ExternalSecretFind
  157. expectError bool
  158. errorMessage string
  159. }{
  160. {
  161. name: "no name specified should return error",
  162. findRef: esv1.ExternalSecretFind{
  163. Name: nil,
  164. },
  165. expectError: true,
  166. errorMessage: "missing field",
  167. },
  168. {
  169. name: "empty name regex should return error",
  170. findRef: esv1.ExternalSecretFind{
  171. Name: &esv1.FindName{
  172. RegExp: "",
  173. },
  174. },
  175. expectError: true,
  176. errorMessage: "missing field",
  177. },
  178. }
  179. for _, tc := range testCases {
  180. t.Run(tc.name, func(t *testing.T) {
  181. _, err := client.GetAllSecrets(context.Background(), tc.findRef)
  182. if tc.expectError {
  183. assert.Error(t, err)
  184. assert.Contains(t, err.Error(), tc.errorMessage)
  185. } else if err != nil {
  186. assert.Contains(t, err.Error(), "barbican client")
  187. }
  188. })
  189. }
  190. }
  191. func TestGetAllSecretsRegexpMatch(t *testing.T) {
  192. type fakeSecret struct {
  193. name string
  194. uuid string
  195. payload string
  196. }
  197. all := []fakeSecret{
  198. {name: "db-a", uuid: "11111111-1111-1111-1111-111111111111", payload: "payload-db-a"},
  199. {name: "db-b", uuid: "22222222-2222-2222-2222-222222222222", payload: "payload-db-b"},
  200. {name: "web-a", uuid: "33333333-3333-3333-3333-333333333333", payload: "payload-web-a"},
  201. }
  202. fakeServer := th.SetupHTTP()
  203. defer fakeServer.Teardown()
  204. // Barbican's list endpoint only does exact-name matching, so mirror that
  205. // here: honor the ?name= query with a literal comparison, like the real
  206. // service does. A regexp value therefore matches nothing server-side, which
  207. // is exactly the reported bug.
  208. fakeServer.Mux.HandleFunc("/secrets", func(w http.ResponseWriter, r *http.Request) {
  209. nameFilter := r.URL.Query().Get("name")
  210. type listed struct {
  211. Name string `json:"name"`
  212. SecretRef string `json:"secret_ref"`
  213. }
  214. var out []listed
  215. for _, s := range all {
  216. if nameFilter != "" && s.name != nameFilter {
  217. continue
  218. }
  219. out = append(out, listed{
  220. Name: s.name,
  221. SecretRef: fmt.Sprintf("http://barbican.example.com/v1/secrets/%s", s.uuid),
  222. })
  223. }
  224. body, _ := json.Marshal(map[string]any{"secrets": out, "total": len(out)})
  225. w.Header().Set("Content-Type", "application/json")
  226. w.WriteHeader(http.StatusOK)
  227. _, _ = w.Write(body)
  228. })
  229. for _, s := range all {
  230. payload := s.payload
  231. fakeServer.Mux.HandleFunc("/secrets/"+s.uuid+"/payload", func(w http.ResponseWriter, _ *http.Request) {
  232. w.Header().Set("Content-Type", "text/plain")
  233. w.WriteHeader(http.StatusOK)
  234. _, _ = w.Write([]byte(payload))
  235. })
  236. }
  237. client := &Client{keyManager: thclient.ServiceClient(fakeServer)}
  238. result, err := client.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{
  239. Name: &esv1.FindName{RegExp: "^db-"},
  240. })
  241. assert.NoError(t, err)
  242. // Only the db-* secrets should match the pattern, not web-a and not just a
  243. // literal secret named "^db-".
  244. assert.Len(t, result, 2)
  245. assert.Equal(t, []byte("payload-db-a"), result["11111111-1111-1111-1111-111111111111"])
  246. assert.Equal(t, []byte("payload-db-b"), result["22222222-2222-2222-2222-222222222222"])
  247. assert.NotContains(t, result, "33333333-3333-3333-3333-333333333333")
  248. }