client.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 infisical implements a provider for retrieving secrets from Infisical.
  14. package infisical
  15. import (
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "strings"
  21. infisical "github.com/infisical/go-sdk"
  22. "github.com/tidwall/gjson"
  23. corev1 "k8s.io/api/core/v1"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. "github.com/external-secrets/external-secrets/providers/v1/infisical/constants"
  26. "github.com/external-secrets/external-secrets/runtime/find"
  27. "github.com/external-secrets/external-secrets/runtime/metrics"
  28. )
  29. var (
  30. errNotImplemented = errors.New("not implemented")
  31. errPropertyNotFound = "property %s does not exist in secret %s"
  32. errTagsNotImplemented = errors.New("find by tags not supported")
  33. )
  34. const (
  35. getSecretsV3 = "GetSecretsV3"
  36. getSecretByKeyV3 = "GetSecretByKeyV3"
  37. )
  38. func getPropertyValue(jsonData, propertyName, keyName string) ([]byte, error) {
  39. result := gjson.Get(jsonData, propertyName)
  40. if !result.Exists() {
  41. return nil, fmt.Errorf(errPropertyNotFound, propertyName, keyName)
  42. }
  43. return []byte(result.Str), nil
  44. }
  45. // getSecretAddress returns the path and key from the given key.
  46. //
  47. // Users can configure a root path, and when a SecretKey is provided with a slash we assume that it is
  48. // within a path appended to the root path.
  49. //
  50. // If the key is not addressing a path at all (i.e. has no `/`), simply return the original
  51. // path and key.
  52. func getSecretAddress(defaultPath, key string) (string, string, error) {
  53. if !strings.Contains(key, "/") {
  54. return defaultPath, key, nil
  55. }
  56. // Check if `key` starts with a `/`, and throw and error if it does not.
  57. if !strings.HasPrefix(key, "/") {
  58. return "", "", fmt.Errorf("a secret key referencing a folder must start with a '/' as it is an absolute path, key: %s", key)
  59. }
  60. // Otherwise, take the prefix from `key` and use that as the path. We intentionally discard
  61. // `defaultPath`.
  62. lastIndex := strings.LastIndex(key, "/")
  63. return key[:lastIndex], key[lastIndex+1:], nil
  64. }
  65. // GetSecret retrieves a secret value from Infisical.
  66. // If this returns an error with type NoSecretError then the secret entry will be deleted depending on the
  67. // deletionPolicy.
  68. func (p *Provider) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  69. path, key, err := getSecretAddress(p.apiScope.SecretPath, ref.Key)
  70. if err != nil {
  71. return nil, err
  72. }
  73. secret, err := p.sdkClient.Secrets().Retrieve(infisical.RetrieveSecretOptions{
  74. Environment: p.apiScope.EnvironmentSlug,
  75. ProjectSlug: p.apiScope.ProjectSlug,
  76. SecretKey: key,
  77. SecretPath: path,
  78. IncludeImports: true,
  79. ExpandSecretReferences: p.apiScope.ExpandSecretReferences,
  80. })
  81. metrics.ObserveAPICall(constants.ProviderName, getSecretByKeyV3, err)
  82. if err != nil {
  83. return nil, err
  84. }
  85. if ref.Property != "" {
  86. propertyValue, err := getPropertyValue(secret.SecretValue, ref.Property, ref.Key)
  87. if err != nil {
  88. return nil, err
  89. }
  90. return propertyValue, nil
  91. }
  92. return []byte(secret.SecretValue), nil
  93. }
  94. // GetSecretMap returns multiple k/v pairs from the provider.
  95. func (p *Provider) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  96. secret, err := p.GetSecret(ctx, ref)
  97. if err != nil {
  98. return nil, err
  99. }
  100. kv := make(map[string]json.RawMessage)
  101. err = json.Unmarshal(secret, &kv)
  102. if err != nil {
  103. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  104. }
  105. secretData := make(map[string][]byte)
  106. for k, v := range kv {
  107. var strVal string
  108. err = json.Unmarshal(v, &strVal)
  109. if err == nil {
  110. secretData[k] = []byte(strVal)
  111. } else {
  112. secretData[k] = v
  113. }
  114. }
  115. return secretData, nil
  116. }
  117. // GetAllSecrets retrieves all secrets matching the given criteria from Infisical.
  118. func (p *Provider) GetAllSecrets(_ context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  119. if ref.Tags != nil {
  120. return nil, errTagsNotImplemented
  121. }
  122. secrets, err := p.sdkClient.Secrets().List(infisical.ListSecretsOptions{
  123. Environment: p.apiScope.EnvironmentSlug,
  124. ProjectSlug: p.apiScope.ProjectSlug,
  125. SecretPath: p.apiScope.SecretPath,
  126. Recursive: p.apiScope.Recursive,
  127. ExpandSecretReferences: p.apiScope.ExpandSecretReferences,
  128. IncludeImports: true,
  129. })
  130. metrics.ObserveAPICall(constants.ProviderName, getSecretsV3, err)
  131. if err != nil {
  132. return nil, err
  133. }
  134. secretMap := make(map[string][]byte)
  135. for _, secret := range secrets {
  136. secretMap[secret.SecretKey] = []byte(secret.SecretValue)
  137. }
  138. if ref.Name == nil && ref.Path == nil {
  139. return secretMap, nil
  140. }
  141. var matcher *find.Matcher
  142. if ref.Name != nil {
  143. m, err := find.New(*ref.Name)
  144. if err != nil {
  145. return nil, err
  146. }
  147. matcher = m
  148. }
  149. selected := map[string][]byte{}
  150. for _, secret := range secrets {
  151. if (matcher != nil && !matcher.MatchName(secret.SecretKey)) || (ref.Path != nil && !strings.HasPrefix(secret.SecretPath, *ref.Path)) {
  152. continue
  153. }
  154. selected[secret.SecretKey] = []byte(secret.SecretValue)
  155. }
  156. return selected, nil
  157. }
  158. // Validate checks if the client is configured correctly.
  159. // and is able to retrieve secrets from the provider.
  160. // If the validation result is unknown it will be ignored.
  161. func (p *Provider) Validate() (esv1.ValidationResult, error) {
  162. // try to fetch the secrets to ensure provided credentials has access to read secrets
  163. _, err := p.sdkClient.Secrets().List(infisical.ListSecretsOptions{
  164. Environment: p.apiScope.EnvironmentSlug,
  165. ProjectSlug: p.apiScope.ProjectSlug,
  166. Recursive: p.apiScope.Recursive,
  167. SecretPath: p.apiScope.SecretPath,
  168. ExpandSecretReferences: p.apiScope.ExpandSecretReferences,
  169. })
  170. metrics.ObserveAPICall(constants.ProviderName, getSecretsV3, err)
  171. if err != nil {
  172. return esv1.ValidationResultError, fmt.Errorf(
  173. "cannot read secrets with provided project scope project:%s environment:%s secret-path:%s recursive:%t, %w",
  174. p.apiScope.ProjectSlug,
  175. p.apiScope.EnvironmentSlug,
  176. p.apiScope.SecretPath,
  177. p.apiScope.Recursive,
  178. err,
  179. )
  180. }
  181. return esv1.ValidationResultReady, nil
  182. }
  183. // PushSecret will write a single secret into the provider.
  184. // This is not implemented for this provider.
  185. func (p *Provider) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
  186. return errNotImplemented
  187. }
  188. // DeleteSecret will delete the secret from a provider.
  189. // This is not implemented for this provider.
  190. func (p *Provider) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  191. return errNotImplemented
  192. }
  193. // SecretExists checks if a secret is already present in the provider at the given location.
  194. // This is not implemented for this provider.
  195. func (p *Provider) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  196. return false, errNotImplemented
  197. }