vault.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package fake
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "reflect"
  19. "strings"
  20. "sync"
  21. vault "github.com/hashicorp/vault/api"
  22. "github.com/external-secrets/external-secrets/providers/v1/vault/util"
  23. )
  24. // LoginFn is a function type that represents logging in to Vault using a specific authentication method.
  25. type LoginFn func(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error)
  26. // Auth is a mock implementation of the Vault authentication interface for testing purposes.
  27. type Auth struct {
  28. LoginFn LoginFn
  29. }
  30. // Login logs in to Vault using the specified authentication method.
  31. func (f Auth) Login(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error) {
  32. return f.LoginFn(ctx, authMethod)
  33. }
  34. type ReadWithDataWithContextFn func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error)
  35. type ListWithContextFn func(ctx context.Context, path string) (*vault.Secret, error)
  36. type WriteWithContextFn func(ctx context.Context, path string, data map[string]any) (*vault.Secret, error)
  37. type DeleteWithContextFn func(ctx context.Context, path string) (*vault.Secret, error)
  38. type Logical struct {
  39. ReadWithDataWithContextFn ReadWithDataWithContextFn
  40. ListWithContextFn ListWithContextFn
  41. WriteWithContextFn WriteWithContextFn
  42. DeleteWithContextFn DeleteWithContextFn
  43. }
  44. func (f Logical) DeleteWithContext(ctx context.Context, path string) (*vault.Secret, error) {
  45. return f.DeleteWithContextFn(ctx, path)
  46. }
  47. func NewDeleteWithContextFn(secret map[string]any, err error) DeleteWithContextFn {
  48. return func(ctx context.Context, path string) (*vault.Secret, error) {
  49. vault := &vault.Secret{
  50. Data: secret,
  51. }
  52. return vault, err
  53. }
  54. }
  55. func buildDataResponse(secret map[string]any, err error) (*vault.Secret, error) {
  56. if secret == nil {
  57. return nil, err
  58. }
  59. return &vault.Secret{Data: secret}, err
  60. }
  61. func buildMetadataResponse(secret map[string]any, err error) (*vault.Secret, error) {
  62. if secret == nil {
  63. return nil, err
  64. }
  65. // If the secret already has the expected metadata structure, return as-is
  66. if _, hasCustomMetadata := secret["custom_metadata"]; hasCustomMetadata {
  67. return &vault.Secret{Data: secret}, err
  68. }
  69. // Otherwise, wrap in custom_metadata for backwards compatibility
  70. metadata := make(map[string]any)
  71. metadata["custom_metadata"] = secret
  72. return &vault.Secret{Data: metadata}, err
  73. }
  74. func NewReadWithContextFn(secret map[string]any, err error) ReadWithDataWithContextFn {
  75. return func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
  76. return buildDataResponse(secret, err)
  77. }
  78. }
  79. func NewReadMetadataWithContextFn(secret map[string]any, err error) ReadWithDataWithContextFn {
  80. return func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
  81. return buildMetadataResponse(secret, err)
  82. }
  83. }
  84. func NewReadWithDataAndMetadataFn(dataSecret, metadataSecret map[string]any, dataErr, metadataErr error) ReadWithDataWithContextFn {
  85. return func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
  86. // Check if this is a metadata path request
  87. if strings.Contains(path, "/metadata/") {
  88. return buildMetadataResponse(metadataSecret, metadataErr)
  89. }
  90. // This is a data path request
  91. return buildDataResponse(dataSecret, dataErr)
  92. }
  93. }
  94. func NewWriteWithContextFn(secret map[string]any, err error) WriteWithContextFn {
  95. return func(ctx context.Context, path string, data map[string]any) (*vault.Secret, error) {
  96. return &vault.Secret{Data: secret}, err
  97. }
  98. }
  99. func ExpectWriteWithContextValue(expected map[string]any) WriteWithContextFn {
  100. return func(ctx context.Context, path string, data map[string]any) (*vault.Secret, error) {
  101. if strings.Contains(path, "metadata") {
  102. return &vault.Secret{Data: data}, nil
  103. }
  104. if !reflect.DeepEqual(expected, data) {
  105. return nil, fmt.Errorf("expected: %v, got: %v", expected, data)
  106. }
  107. return &vault.Secret{Data: data}, nil
  108. }
  109. }
  110. func ExpectWriteWithContextNoCall() WriteWithContextFn {
  111. return func(_ context.Context, path string, data map[string]any) (*vault.Secret, error) {
  112. return nil, errors.New("fail")
  113. }
  114. }
  115. func ExpectDeleteWithContextNoCall() DeleteWithContextFn {
  116. return func(ctx context.Context, path string) (*vault.Secret, error) {
  117. return nil, errors.New("fail")
  118. }
  119. }
  120. // ReadWithDataWithContext reads the secret at the specified path in Vault with additional data.
  121. func (f Logical) ReadWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
  122. return f.ReadWithDataWithContextFn(ctx, path, data)
  123. }
  124. // ListWithContext lists the secrets at the specified path in Vault.
  125. func (f Logical) ListWithContext(ctx context.Context, path string) (*vault.Secret, error) {
  126. return f.ListWithContextFn(ctx, path)
  127. }
  128. // WriteWithContext writes data to the specified path in Vault.
  129. func (f Logical) WriteWithContext(ctx context.Context, path string, data map[string]any) (*vault.Secret, error) {
  130. return f.WriteWithContextFn(ctx, path, data)
  131. }
  132. // RevokeSelfWithContextFn is a function type that represents revoking the Vault token associated with the current client.
  133. type RevokeSelfWithContextFn func(ctx context.Context, token string) error
  134. // LookupSelfWithContextFn is a function type that represents looking up the Vault token associated with the current client.
  135. type LookupSelfWithContextFn func(ctx context.Context) (*vault.Secret, error)
  136. // Token is a mock implementation of the Vault token interface for testing purposes.
  137. type Token struct {
  138. RevokeSelfWithContextFn RevokeSelfWithContextFn
  139. LookupSelfWithContextFn LookupSelfWithContextFn
  140. }
  141. // RevokeSelfWithContext revokes the token associated with the current client.
  142. func (f Token) RevokeSelfWithContext(ctx context.Context, token string) error {
  143. return f.RevokeSelfWithContextFn(ctx, token)
  144. }
  145. // LookupSelfWithContext looks up the token associated with the current client.
  146. func (f Token) LookupSelfWithContext(ctx context.Context) (*vault.Secret, error) {
  147. return f.LookupSelfWithContextFn(ctx)
  148. }
  149. // MockSetTokenFn is a function type that represents setting the Vault token.
  150. type MockSetTokenFn func(v string)
  151. // MockTokenFn is a function type that represents getting the Vault token.
  152. type MockTokenFn func() string
  153. // MockClearTokenFn is a function type that represents clearing the Vault token.
  154. type MockClearTokenFn func()
  155. // MockNamespaceFn is a function type that represents getting the Vault namespace.
  156. type MockNamespaceFn func() string
  157. // MockSetNamespaceFn is a function type that represents setting the Vault namespace.
  158. type MockSetNamespaceFn func(namespace string)
  159. // MockAddHeaderFn is a function type that represents adding a header to the Vault client requests.
  160. type MockAddHeaderFn func(key, value string)
  161. // VaultListResponse is a struct to represent the response from a Vault list operation.
  162. type VaultListResponse struct {
  163. Metadata *vault.Response
  164. Data *vault.Response
  165. }
  166. // NewAuthTokenFn returns a MockAuthToken that always returns a nil secret and nil error.
  167. func NewAuthTokenFn() Token {
  168. return Token{nil, func(context.Context) (*vault.Secret, error) {
  169. return &(vault.Secret{}), nil
  170. }}
  171. }
  172. // NewSetTokenFn returns a MockSetTokenFn that calls the provided functions in order.
  173. func NewSetTokenFn(ofn ...func(v string)) MockSetTokenFn {
  174. return func(v string) {
  175. for _, fn := range ofn {
  176. fn(v)
  177. }
  178. }
  179. }
  180. // NewTokenFn returns a MockTokenFn that always returns the provided string.
  181. func NewTokenFn(v string) MockTokenFn {
  182. return func() string {
  183. return v
  184. }
  185. }
  186. // VaultClient is a mock implementation of the Vault client interface for testing purposes.
  187. type VaultClient struct {
  188. MockLogical Logical
  189. MockAuth Auth
  190. MockAuthToken Token
  191. MockSetToken MockSetTokenFn
  192. MockToken MockTokenFn
  193. MockClearToken MockClearTokenFn
  194. MockNamespace MockNamespaceFn
  195. MockSetNamespace MockSetNamespaceFn
  196. MockAddHeader MockAddHeaderFn
  197. namespace string
  198. lock sync.RWMutex
  199. }
  200. // Logical returns the mock Logical.
  201. func (c *VaultClient) Logical() Logical {
  202. return c.MockLogical
  203. }
  204. // NewVaultLogical returns a new vault Logical instance.
  205. func NewVaultLogical() Logical {
  206. logical := Logical{
  207. ReadWithDataWithContextFn: func(context.Context, string, map[string][]string) (*vault.Secret, error) {
  208. return nil, nil
  209. },
  210. ListWithContextFn: func(context.Context, string) (*vault.Secret, error) {
  211. return nil, nil
  212. },
  213. WriteWithContextFn: func(context.Context, string, map[string]any) (*vault.Secret, error) {
  214. return nil, nil
  215. },
  216. }
  217. return logical
  218. }
  219. // Auth returns the mock authentication.
  220. func (c *VaultClient) Auth() Auth {
  221. return c.MockAuth
  222. }
  223. // NewVaultAuth returns a mock authentication Auth.
  224. func NewVaultAuth() Auth {
  225. auth := Auth{
  226. LoginFn: func(context.Context, vault.AuthMethod) (*vault.Secret, error) {
  227. return nil, nil
  228. },
  229. }
  230. return auth
  231. }
  232. // AuthToken returns the mock authentication token interface.
  233. func (c *VaultClient) AuthToken() Token {
  234. return c.MockAuthToken
  235. }
  236. // SetToken sets the authentication token.
  237. func (c *VaultClient) SetToken(v string) {
  238. c.MockSetToken(v)
  239. }
  240. // Token returns the current authentication token.
  241. func (c *VaultClient) Token() string {
  242. return c.MockToken()
  243. }
  244. // ClearToken clears the current authentication token.
  245. func (c *VaultClient) ClearToken() {
  246. c.MockClearToken()
  247. }
  248. // Namespace returns the current Vault namespace.
  249. func (c *VaultClient) Namespace() string {
  250. c.lock.RLock()
  251. defer c.lock.RUnlock()
  252. ns := c.namespace
  253. return ns
  254. }
  255. // SetNamespace sets the Vault namespace.
  256. func (c *VaultClient) SetNamespace(namespace string) {
  257. c.lock.Lock()
  258. defer c.lock.Unlock()
  259. c.namespace = namespace
  260. }
  261. // AddHeader adds a header to the Vault client requests.
  262. func (c *VaultClient) AddHeader(key, value string) {
  263. c.MockAddHeader(key, value)
  264. }
  265. // ClientWithLoginMock returns a client with mocked login functionality.
  266. func ClientWithLoginMock(config *vault.Config) (vaultutil.Client, error) {
  267. return clientWithLoginMockOptions(config)
  268. }
  269. // ModifiableClientWithLoginMock returns a factory function that creates clients with customizable mock behavior.
  270. func ModifiableClientWithLoginMock(opts ...func(cl *VaultClient)) func(config *vault.Config) (vaultutil.Client, error) {
  271. return func(config *vault.Config) (vaultutil.Client, error) {
  272. return clientWithLoginMockOptions(config, opts...)
  273. }
  274. }
  275. func clientWithLoginMockOptions(_ *vault.Config, opts ...func(cl *VaultClient)) (vaultutil.Client, error) {
  276. cl := &VaultClient{
  277. MockAuthToken: NewAuthTokenFn(),
  278. MockSetToken: NewSetTokenFn(),
  279. MockToken: NewTokenFn(""),
  280. MockAuth: NewVaultAuth(),
  281. MockLogical: NewVaultLogical(),
  282. }
  283. for _, opt := range opts {
  284. opt(cl)
  285. }
  286. return &vaultutil.VaultClient{
  287. SetTokenFunc: cl.SetToken,
  288. TokenFunc: cl.Token,
  289. ClearTokenFunc: cl.ClearToken,
  290. AuthField: cl.Auth(),
  291. AuthTokenField: cl.AuthToken(),
  292. LogicalField: cl.Logical(),
  293. NamespaceFunc: cl.Namespace,
  294. SetNamespaceFunc: cl.SetNamespace,
  295. AddHeaderFunc: cl.AddHeader,
  296. }, nil
  297. }