client.go 14 KB

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