client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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 grpc
  13. import (
  14. "context"
  15. "fmt"
  16. "time"
  17. "github.com/go-logr/logr"
  18. "google.golang.org/grpc"
  19. "google.golang.org/grpc/connectivity"
  20. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  21. pb "github.com/external-secrets/external-secrets/proto/provider"
  22. v2 "github.com/external-secrets/external-secrets/providers/v2/common"
  23. )
  24. const (
  25. // defaultTimeout is the default timeout for gRPC calls
  26. defaultTimeout = 30 * time.Second
  27. )
  28. // grpcProviderClient implements the v2.Provider interface using gRPC.
  29. type grpcProviderClient struct {
  30. conn *grpc.ClientConn
  31. client pb.SecretStoreProviderClient
  32. log logr.Logger
  33. }
  34. // Ensure grpcProviderClient implements Provider interface
  35. var _ v2.Provider = &grpcProviderClient{}
  36. // GetSecret retrieves a single secret from the provider via gRPC.
  37. func (c *grpcProviderClient) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) ([]byte, error) {
  38. start := time.Now()
  39. var err error
  40. defer func() {
  41. clientMetrics.ObserveRequest("GetSecret", c.conn.Target(), err, time.Since(start))
  42. }()
  43. c.log.V(1).Info("getting secret via gRPC",
  44. "key", ref.Key,
  45. "version", ref.Version,
  46. "property", ref.Property,
  47. "connectionState", c.conn.GetState().String(),
  48. "providerRef", providerRef,
  49. "sourceNamespace", sourceNamespace)
  50. // Check connection state before call
  51. state := c.conn.GetState()
  52. if state != connectivity.Ready && state != connectivity.Idle {
  53. c.log.Info("connection not ready, attempting to reconnect",
  54. "state", state.String(),
  55. "target", c.conn.Target())
  56. }
  57. // Create context with timeout
  58. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  59. defer cancel()
  60. // Convert v1 reference to protobuf message
  61. pbRef := &pb.ExternalSecretDataRemoteRef{
  62. Key: ref.Key,
  63. Version: ref.Version,
  64. Property: ref.Property,
  65. DecodingStrategy: string(ref.DecodingStrategy),
  66. MetadataPolicy: string(ref.MetadataPolicy),
  67. }
  68. // Make gRPC call with provider reference
  69. req := &pb.GetSecretRequest{
  70. RemoteRef: pbRef,
  71. ProviderRef: providerRef,
  72. SourceNamespace: sourceNamespace,
  73. }
  74. c.log.V(1).Info("calling GetSecret RPC",
  75. "target", c.conn.Target(),
  76. "timeout", defaultTimeout.String())
  77. resp, err := c.client.GetSecret(ctx, req)
  78. if err != nil {
  79. c.log.Error(err, "GetSecret RPC failed",
  80. "key", ref.Key,
  81. "connectionState", c.conn.GetState().String(),
  82. "target", c.conn.Target())
  83. err = fmt.Errorf("failed to get secret via gRPC: %w", err)
  84. return nil, err
  85. }
  86. c.log.V(1).Info("GetSecret RPC succeeded",
  87. "key", ref.Key,
  88. "valueLength", len(resp.Value))
  89. return resp.Value, nil
  90. }
  91. // GetSecretMap retrieves multiple key/value pairs from a single provider object via gRPC.
  92. func (c *grpcProviderClient) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
  93. start := time.Now()
  94. var err error
  95. defer func() {
  96. clientMetrics.ObserveRequest("GetSecretMap", c.conn.Target(), err, time.Since(start))
  97. }()
  98. c.log.V(1).Info("getting secret map via gRPC",
  99. "key", ref.Key,
  100. "version", ref.Version,
  101. "property", ref.Property,
  102. "connectionState", c.conn.GetState().String(),
  103. "providerRef", providerRef,
  104. "sourceNamespace", sourceNamespace)
  105. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  106. defer cancel()
  107. pbRef := &pb.ExternalSecretDataRemoteRef{
  108. Key: ref.Key,
  109. Version: ref.Version,
  110. Property: ref.Property,
  111. DecodingStrategy: string(ref.DecodingStrategy),
  112. MetadataPolicy: string(ref.MetadataPolicy),
  113. }
  114. req := &pb.GetSecretMapRequest{
  115. RemoteRef: pbRef,
  116. ProviderRef: providerRef,
  117. SourceNamespace: sourceNamespace,
  118. }
  119. c.log.V(1).Info("calling GetSecretMap RPC",
  120. "target", c.conn.Target())
  121. resp, err := c.client.GetSecretMap(ctx, req)
  122. if err != nil {
  123. c.log.Error(err, "GetSecretMap RPC failed",
  124. "connectionState", c.conn.GetState().String(),
  125. "target", c.conn.Target())
  126. err = fmt.Errorf("failed to get secret map via gRPC: %w", err)
  127. return nil, err
  128. }
  129. c.log.V(1).Info("GetSecretMap RPC succeeded",
  130. "secretCount", len(resp.Secrets))
  131. return resp.Secrets, nil
  132. }
  133. // Validate checks if the provider is properly configured via gRPC.
  134. func (c *grpcProviderClient) Validate(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) error {
  135. start := time.Now()
  136. var err error
  137. defer func() {
  138. clientMetrics.ObserveRequest("Validate", c.conn.Target(), err, time.Since(start))
  139. }()
  140. c.log.Info("validating provider via gRPC",
  141. "target", c.conn.Target(),
  142. "connectionState", c.conn.GetState().String(),
  143. "providerRef", providerRef,
  144. "sourceNamespace", sourceNamespace)
  145. // Check connection state before call
  146. state := c.conn.GetState()
  147. c.log.V(1).Info("connection details",
  148. "state", state.String(),
  149. "target", c.conn.Target(),
  150. "authority", c.conn.GetMethodConfig("").WaitForReady)
  151. if state != connectivity.Ready && state != connectivity.Idle {
  152. c.log.Info("connection not in ready/idle state, will attempt to connect",
  153. "state", state.String(),
  154. "target", c.conn.Target())
  155. }
  156. // Create context with timeout
  157. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  158. defer cancel()
  159. // Make gRPC call with provider reference
  160. req := &pb.ValidateRequest{
  161. ProviderRef: providerRef,
  162. SourceNamespace: sourceNamespace,
  163. }
  164. c.log.V(1).Info("calling Validate RPC",
  165. "target", c.conn.Target(),
  166. "timeout", defaultTimeout.String())
  167. resp, err := c.client.Validate(ctx, req)
  168. if err != nil {
  169. c.log.Error(err, "Validate RPC failed",
  170. "connectionState", c.conn.GetState().String(),
  171. "target", c.conn.Target(),
  172. "errorType", fmt.Sprintf("%T", err))
  173. err = fmt.Errorf("failed to validate provider via gRPC: %w", err)
  174. return err
  175. }
  176. c.log.V(1).Info("Validate RPC completed",
  177. "valid", resp.Valid,
  178. "error", resp.Error)
  179. // Check for error in response
  180. if !resp.Valid {
  181. if resp.Error != "" {
  182. c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
  183. "message", resp.Error)
  184. err = fmt.Errorf("provider validation failed: %s", resp.Error)
  185. return err
  186. }
  187. c.log.Error(fmt.Errorf("provider validation failed"), "validation response",
  188. "message", "no error message provided")
  189. err = fmt.Errorf("provider validation failed without error message")
  190. return err
  191. }
  192. c.log.Info("provider validation succeeded")
  193. return nil
  194. }
  195. // GetAllSecrets retrieves multiple secrets based on find criteria via gRPC.
  196. func (c *grpcProviderClient) GetAllSecrets(ctx context.Context, find esv1.ExternalSecretFind, providerRef *pb.ProviderReference, sourceNamespace string) (map[string][]byte, error) {
  197. start := time.Now()
  198. var err error
  199. defer func() {
  200. clientMetrics.ObserveRequest("GetAllSecrets", c.conn.Target(), err, time.Since(start))
  201. }()
  202. c.log.V(1).Info("getting all secrets via gRPC",
  203. "tags", find.Tags,
  204. "connectionState", c.conn.GetState().String(),
  205. "providerRef", providerRef,
  206. "sourceNamespace", sourceNamespace)
  207. // Create context with timeout
  208. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  209. defer cancel()
  210. // Convert find criteria to protobuf
  211. pbFind := &pb.ExternalSecretFind{
  212. Tags: find.Tags,
  213. ConversionStrategy: string(find.ConversionStrategy),
  214. DecodingStrategy: string(find.DecodingStrategy),
  215. }
  216. if find.Path != nil {
  217. pbFind.Path = *find.Path
  218. }
  219. if find.Name != nil {
  220. pbFind.Name = &pb.FindName{
  221. Regexp: find.Name.RegExp,
  222. }
  223. }
  224. // Make gRPC call
  225. req := &pb.GetAllSecretsRequest{
  226. ProviderRef: providerRef,
  227. Find: pbFind,
  228. SourceNamespace: sourceNamespace,
  229. }
  230. c.log.V(1).Info("calling GetAllSecrets RPC",
  231. "target", c.conn.Target())
  232. resp, err := c.client.GetAllSecrets(ctx, req)
  233. if err != nil {
  234. c.log.Error(err, "GetAllSecrets RPC failed",
  235. "connectionState", c.conn.GetState().String(),
  236. "target", c.conn.Target())
  237. err = fmt.Errorf("failed to get all secrets via gRPC: %w", err)
  238. return nil, err
  239. }
  240. c.log.V(1).Info("GetAllSecrets RPC succeeded",
  241. "secretCount", len(resp.Secrets))
  242. return resp.Secrets, nil
  243. }
  244. // PushSecret writes a secret to the provider via gRPC.
  245. func (c *grpcProviderClient) PushSecret(ctx context.Context, secretData map[string][]byte, pushSecretData *pb.PushSecretData, providerRef *pb.ProviderReference, sourceNamespace string) error {
  246. start := time.Now()
  247. var err error
  248. defer func() {
  249. clientMetrics.ObserveRequest("PushSecret", c.conn.Target(), err, time.Since(start))
  250. }()
  251. c.log.V(1).Info("pushing secret via gRPC",
  252. "remoteKey", pushSecretData.RemoteKey,
  253. "property", pushSecretData.Property,
  254. "connectionState", c.conn.GetState().String(),
  255. "providerRef", providerRef,
  256. "sourceNamespace", sourceNamespace)
  257. // Create context with timeout
  258. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  259. defer cancel()
  260. // Make gRPC call
  261. req := &pb.PushSecretRequest{
  262. ProviderRef: providerRef,
  263. SecretData: secretData,
  264. PushSecretData: pushSecretData,
  265. SourceNamespace: sourceNamespace,
  266. }
  267. c.log.V(1).Info("calling PushSecret RPC",
  268. "target", c.conn.Target())
  269. _, err = c.client.PushSecret(ctx, req)
  270. if err != nil {
  271. c.log.Error(err, "PushSecret RPC failed",
  272. "connectionState", c.conn.GetState().String(),
  273. "target", c.conn.Target())
  274. err = fmt.Errorf("failed to push secret via gRPC: %w", err)
  275. return err
  276. }
  277. c.log.V(1).Info("PushSecret RPC succeeded",
  278. "remoteKey", pushSecretData.RemoteKey)
  279. return nil
  280. }
  281. // DeleteSecret deletes a secret from the provider via gRPC.
  282. func (c *grpcProviderClient) DeleteSecret(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) error {
  283. start := time.Now()
  284. var err error
  285. defer func() {
  286. clientMetrics.ObserveRequest("DeleteSecret", c.conn.Target(), err, time.Since(start))
  287. }()
  288. c.log.V(1).Info("deleting secret via gRPC",
  289. "remoteKey", remoteRef.RemoteKey,
  290. "property", remoteRef.Property,
  291. "connectionState", c.conn.GetState().String(),
  292. "providerRef", providerRef,
  293. "sourceNamespace", sourceNamespace)
  294. // Create context with timeout
  295. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  296. defer cancel()
  297. // Make gRPC call
  298. req := &pb.DeleteSecretRequest{
  299. ProviderRef: providerRef,
  300. RemoteRef: remoteRef,
  301. SourceNamespace: sourceNamespace,
  302. }
  303. c.log.V(1).Info("calling DeleteSecret RPC",
  304. "target", c.conn.Target())
  305. _, err = c.client.DeleteSecret(ctx, req)
  306. if err != nil {
  307. c.log.Error(err, "DeleteSecret RPC failed",
  308. "connectionState", c.conn.GetState().String(),
  309. "target", c.conn.Target())
  310. err = fmt.Errorf("failed to delete secret via gRPC: %w", err)
  311. return err
  312. }
  313. c.log.V(1).Info("DeleteSecret RPC succeeded",
  314. "remoteKey", remoteRef.RemoteKey)
  315. return nil
  316. }
  317. // SecretExists checks if a secret exists in the provider via gRPC.
  318. func (c *grpcProviderClient) SecretExists(ctx context.Context, remoteRef *pb.PushSecretRemoteRef, providerRef *pb.ProviderReference, sourceNamespace string) (bool, error) {
  319. start := time.Now()
  320. var err error
  321. defer func() {
  322. clientMetrics.ObserveRequest("SecretExists", c.conn.Target(), err, time.Since(start))
  323. }()
  324. c.log.V(1).Info("checking if secret exists via gRPC",
  325. "remoteKey", remoteRef.RemoteKey,
  326. "property", remoteRef.Property,
  327. "connectionState", c.conn.GetState().String(),
  328. "providerRef", providerRef,
  329. "sourceNamespace", sourceNamespace)
  330. // Create context with timeout
  331. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  332. defer cancel()
  333. // Make gRPC call
  334. req := &pb.SecretExistsRequest{
  335. ProviderRef: providerRef,
  336. RemoteRef: remoteRef,
  337. SourceNamespace: sourceNamespace,
  338. }
  339. c.log.V(1).Info("calling SecretExists RPC",
  340. "target", c.conn.Target())
  341. resp, err := c.client.SecretExists(ctx, req)
  342. if err != nil {
  343. c.log.Error(err, "SecretExists RPC failed",
  344. "connectionState", c.conn.GetState().String(),
  345. "target", c.conn.Target())
  346. err = fmt.Errorf("failed to check if secret exists via gRPC: %w", err)
  347. return false, err
  348. }
  349. c.log.V(1).Info("SecretExists RPC succeeded",
  350. "remoteKey", remoteRef.RemoteKey,
  351. "exists", resp.Exists)
  352. return resp.Exists, nil
  353. }
  354. // Capabilities retrieves the capabilities of the provider via gRPC.
  355. func (c *grpcProviderClient) Capabilities(ctx context.Context, providerRef *pb.ProviderReference, sourceNamespace string) (pb.SecretStoreCapabilities, error) {
  356. start := time.Now()
  357. var err error
  358. defer func() {
  359. clientMetrics.ObserveRequest("Capabilities", c.conn.Target(), err, time.Since(start))
  360. }()
  361. c.log.V(1).Info("getting provider capabilities via gRPC",
  362. "target", c.conn.Target(),
  363. "connectionState", c.conn.GetState().String(),
  364. "providerRef", providerRef,
  365. "sourceNamespace", sourceNamespace)
  366. // Create context with timeout
  367. ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
  368. defer cancel()
  369. // Make gRPC call with provider reference
  370. req := &pb.CapabilitiesRequest{
  371. ProviderRef: providerRef,
  372. SourceNamespace: sourceNamespace,
  373. }
  374. c.log.V(1).Info("calling Capabilities RPC",
  375. "target", c.conn.Target())
  376. resp, err := c.client.Capabilities(ctx, req)
  377. if err != nil {
  378. c.log.Error(err, "Capabilities RPC failed",
  379. "connectionState", c.conn.GetState().String(),
  380. "target", c.conn.Target())
  381. err = fmt.Errorf("failed to get capabilities via gRPC: %w", err)
  382. return pb.SecretStoreCapabilities_READ_ONLY, err
  383. }
  384. c.log.V(1).Info("Capabilities RPC succeeded",
  385. "capabilities", resp.Capabilities)
  386. return resp.Capabilities, nil
  387. }
  388. // Close closes the gRPC connection.
  389. func (c *grpcProviderClient) Close(ctx context.Context) error {
  390. if c.conn != nil {
  391. c.log.V(1).Info("closing gRPC connection",
  392. "target", c.conn.Target(),
  393. "state", c.conn.GetState().String())
  394. return c.conn.Close()
  395. }
  396. c.log.V(1).Info("no connection to close")
  397. return nil
  398. }