provider.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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 ibm
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. "strconv"
  18. "strings"
  19. "time"
  20. core "github.com/IBM/go-sdk-core/v5/core"
  21. sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
  22. gjson "github.com/tidwall/gjson"
  23. corev1 "k8s.io/api/core/v1"
  24. types "k8s.io/apimachinery/pkg/types"
  25. ctrl "sigs.k8s.io/controller-runtime"
  26. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. utils "github.com/external-secrets/external-secrets/pkg/utils"
  29. )
  30. const (
  31. SecretsManagerEndpointEnv = "IBM_SECRETSMANAGER_ENDPOINT"
  32. STSEndpointEnv = "IBM_STS_ENDPOINT"
  33. SSMEndpointEnv = "IBM_SSM_ENDPOINT"
  34. errIBMClient = "cannot setup new ibm client: %w"
  35. errIBMCredSecretName = "invalid IBM SecretStore resource: missing IBM APIKey"
  36. errUninitalizedIBMProvider = "provider IBM is not initialized"
  37. errInvalidClusterStoreMissingSKNamespace = "invalid ClusterStore, missing namespace"
  38. errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
  39. errMissingSAK = "missing SecretAccessKey"
  40. errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
  41. )
  42. // https://github.com/external-secrets/external-secrets/issues/644
  43. var _ esv1beta1.SecretsClient = &providerIBM{}
  44. var _ esv1beta1.Provider = &providerIBM{}
  45. type SecretManagerClient interface {
  46. GetSecret(getSecretOptions *sm.GetSecretOptions) (result *sm.GetSecret, response *core.DetailedResponse, err error)
  47. }
  48. type providerIBM struct {
  49. IBMClient SecretManagerClient
  50. }
  51. type client struct {
  52. kube kclient.Client
  53. store *esv1beta1.IBMProvider
  54. namespace string
  55. storeKind string
  56. credentials []byte
  57. }
  58. var log = ctrl.Log.WithName("provider").WithName("ibm").WithName("secretsmanager")
  59. func (c *client) setAuth(ctx context.Context) error {
  60. credentialsSecret := &corev1.Secret{}
  61. credentialsSecretName := c.store.Auth.SecretRef.SecretAPIKey.Name
  62. if credentialsSecretName == "" {
  63. return fmt.Errorf(errIBMCredSecretName)
  64. }
  65. objectKey := types.NamespacedName{
  66. Name: credentialsSecretName,
  67. Namespace: c.namespace,
  68. }
  69. // only ClusterStore is allowed to set namespace (and then it's required)
  70. if c.storeKind == esv1beta1.ClusterSecretStoreKind {
  71. if c.store.Auth.SecretRef.SecretAPIKey.Namespace == nil {
  72. return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
  73. }
  74. objectKey.Namespace = *c.store.Auth.SecretRef.SecretAPIKey.Namespace
  75. }
  76. err := c.kube.Get(ctx, objectKey, credentialsSecret)
  77. if err != nil {
  78. return fmt.Errorf(errFetchSAKSecret, err)
  79. }
  80. c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAPIKey.Key]
  81. if (c.credentials == nil) || (len(c.credentials) == 0) {
  82. return fmt.Errorf(errMissingSAK)
  83. }
  84. return nil
  85. }
  86. // Empty GetAllSecrets.
  87. func (ibm *providerIBM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  88. // TO be implemented
  89. return nil, fmt.Errorf("GetAllSecrets not implemented")
  90. }
  91. func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  92. if utils.IsNil(ibm.IBMClient) {
  93. return nil, fmt.Errorf(errUninitalizedIBMProvider)
  94. }
  95. secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
  96. secretName := ref.Key
  97. nameSplitted := strings.Split(secretName, "/")
  98. if len(nameSplitted) > 1 {
  99. secretType = nameSplitted[0]
  100. secretName = nameSplitted[1]
  101. }
  102. switch secretType {
  103. case sm.GetSecretOptionsSecretTypeArbitraryConst:
  104. return getArbitrarySecret(ibm, &secretName)
  105. case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
  106. if ref.Property == "" {
  107. return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
  108. }
  109. return getUsernamePasswordSecret(ibm, &secretName, ref)
  110. case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
  111. return getIamCredentialsSecret(ibm, &secretName)
  112. case sm.CreateSecretOptionsSecretTypeImportedCertConst:
  113. if ref.Property == "" {
  114. return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
  115. }
  116. return getImportCertSecret(ibm, &secretName, ref)
  117. case sm.CreateSecretOptionsSecretTypePublicCertConst:
  118. if ref.Property == "" {
  119. return nil, fmt.Errorf("remoteRef.property required for secret type public_cert")
  120. }
  121. return getPublicCertSecret(ibm, &secretName, ref)
  122. case sm.CreateSecretOptionsSecretTypeKvConst:
  123. return getKVSecret(ibm, &secretName, ref)
  124. default:
  125. return nil, fmt.Errorf("unknown secret type %s", secretType)
  126. }
  127. }
  128. func getArbitrarySecret(ibm *providerIBM, secretName *string) ([]byte, error) {
  129. response, _, err := ibm.IBMClient.GetSecret(
  130. &sm.GetSecretOptions{
  131. SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
  132. ID: secretName,
  133. })
  134. if err != nil {
  135. return nil, err
  136. }
  137. secret := response.Resources[0].(*sm.SecretResource)
  138. secretData := secret.SecretData.(map[string]interface{})
  139. arbitrarySecretPayload := secretData["payload"].(string)
  140. return []byte(arbitrarySecretPayload), nil
  141. }
  142. func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  143. response, _, err := ibm.IBMClient.GetSecret(
  144. &sm.GetSecretOptions{
  145. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  146. ID: secretName,
  147. })
  148. if err != nil {
  149. return nil, err
  150. }
  151. secret := response.Resources[0].(*sm.SecretResource)
  152. secretData := secret.SecretData.(map[string]interface{})
  153. if val, ok := secretData[ref.Property]; ok {
  154. return []byte(val.(string)), nil
  155. }
  156. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  157. }
  158. func getPublicCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  159. response, _, err := ibm.IBMClient.GetSecret(
  160. &sm.GetSecretOptions{
  161. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
  162. ID: secretName,
  163. })
  164. if err != nil {
  165. return nil, err
  166. }
  167. secret := response.Resources[0].(*sm.SecretResource)
  168. secretData := secret.SecretData.(map[string]interface{})
  169. if val, ok := secretData[ref.Property]; ok {
  170. return []byte(val.(string)), nil
  171. }
  172. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  173. }
  174. func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
  175. response, _, err := ibm.IBMClient.GetSecret(
  176. &sm.GetSecretOptions{
  177. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  178. ID: secretName,
  179. })
  180. if err != nil {
  181. return nil, err
  182. }
  183. secret := response.Resources[0].(*sm.SecretResource)
  184. secretData := *secret.APIKey
  185. return []byte(secretData), nil
  186. }
  187. func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  188. response, _, err := ibm.IBMClient.GetSecret(
  189. &sm.GetSecretOptions{
  190. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  191. ID: secretName,
  192. })
  193. if err != nil {
  194. return nil, err
  195. }
  196. secret := response.Resources[0].(*sm.SecretResource)
  197. secretData := secret.SecretData.(map[string]interface{})
  198. if val, ok := secretData[ref.Property]; ok {
  199. return []byte(val.(string)), nil
  200. }
  201. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  202. }
  203. // Returns a secret of type kv and supports json path.
  204. func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  205. secret, err := getSecretByType(ibm, secretName, sm.CreateSecretOptionsSecretTypeKvConst)
  206. if err != nil {
  207. return nil, err
  208. }
  209. log.Info("fetching secret", "secretName", secretName, "key", ref.Key)
  210. secretData := secret.SecretData.(map[string]interface{})
  211. payload, ok := secretData["payload"]
  212. if !ok {
  213. return nil, fmt.Errorf("no payload returned for secret %s", ref.Key)
  214. }
  215. payloadJSON := payload
  216. payloadJSONMap, ok := payloadJSON.(map[string]interface{})
  217. if ok {
  218. var payloadJSONByte []byte
  219. payloadJSONByte, err = json.Marshal(payloadJSONMap)
  220. if err != nil {
  221. return nil, fmt.Errorf("marshaling payload from secret failed. %w", err)
  222. }
  223. payloadJSON = string(payloadJSONByte)
  224. }
  225. // no property requested, return the entire payload
  226. if ref.Property == "" {
  227. return []byte(payloadJSON.(string)), nil
  228. }
  229. // returns the requested key
  230. // consider that the key contains a ".". this could be one of 2 options
  231. // a) "." is part of the key name
  232. // b) "." is symbole for JSON path
  233. if ref.Property != "" {
  234. refProperty := ref.Property
  235. // a) "." is part the key name
  236. // escape "."
  237. idx := strings.Index(refProperty, ".")
  238. if idx > 0 {
  239. refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
  240. val := gjson.Get(payloadJSON.(string), refProperty)
  241. if val.Exists() {
  242. return []byte(val.String()), nil
  243. }
  244. }
  245. // b) "." is symbole for JSON path
  246. // try to get value for this path
  247. val := gjson.Get(payloadJSON.(string), ref.Property)
  248. if !val.Exists() {
  249. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  250. }
  251. return []byte(val.String()), nil
  252. }
  253. return nil, fmt.Errorf("no property provided for secret %s", ref.Key)
  254. }
  255. func getSecretByType(ibm *providerIBM, secretName *string, secretType string) (*sm.SecretResource, error) {
  256. response, _, err := ibm.IBMClient.GetSecret(
  257. &sm.GetSecretOptions{
  258. SecretType: core.StringPtr(secretType),
  259. ID: secretName,
  260. })
  261. if err != nil {
  262. return nil, err
  263. }
  264. secret := response.Resources[0].(*sm.SecretResource)
  265. return secret, nil
  266. }
  267. func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  268. if utils.IsNil(ibm.IBMClient) {
  269. return nil, fmt.Errorf(errUninitalizedIBMProvider)
  270. }
  271. secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
  272. secretName := ref.Key
  273. nameSplitted := strings.Split(secretName, "/")
  274. if len(nameSplitted) > 1 {
  275. secretType = nameSplitted[0]
  276. secretName = nameSplitted[1]
  277. }
  278. switch secretType {
  279. case sm.GetSecretOptionsSecretTypeArbitraryConst:
  280. response, _, err := ibm.IBMClient.GetSecret(
  281. &sm.GetSecretOptions{
  282. SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
  283. ID: &ref.Key,
  284. })
  285. if err != nil {
  286. return nil, err
  287. }
  288. secret := response.Resources[0].(*sm.SecretResource)
  289. secretData := secret.SecretData.(map[string]interface{})
  290. arbitrarySecretPayload := secretData["payload"].(string)
  291. kv := make(map[string]interface{})
  292. err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
  293. if err != nil {
  294. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  295. }
  296. secretMap := byteArrayMap(kv)
  297. return secretMap, nil
  298. case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
  299. response, _, err := ibm.IBMClient.GetSecret(
  300. &sm.GetSecretOptions{
  301. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  302. ID: &secretName,
  303. })
  304. if err != nil {
  305. return nil, err
  306. }
  307. secret := response.Resources[0].(*sm.SecretResource)
  308. secretData := secret.SecretData.(map[string]interface{})
  309. secretMap := byteArrayMap(secretData)
  310. return secretMap, nil
  311. case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
  312. response, _, err := ibm.IBMClient.GetSecret(
  313. &sm.GetSecretOptions{
  314. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  315. ID: &secretName,
  316. })
  317. if err != nil {
  318. return nil, err
  319. }
  320. secret := response.Resources[0].(*sm.SecretResource)
  321. secretData := *secret.APIKey
  322. secretMap := make(map[string][]byte)
  323. secretMap["apikey"] = []byte(secretData)
  324. return secretMap, nil
  325. case sm.CreateSecretOptionsSecretTypeImportedCertConst:
  326. response, _, err := ibm.IBMClient.GetSecret(
  327. &sm.GetSecretOptions{
  328. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  329. ID: &secretName,
  330. })
  331. if err != nil {
  332. return nil, err
  333. }
  334. secret := response.Resources[0].(*sm.SecretResource)
  335. secretData := secret.SecretData.(map[string]interface{})
  336. secretMap := byteArrayMap(secretData)
  337. return secretMap, nil
  338. case sm.CreateSecretOptionsSecretTypePublicCertConst:
  339. response, _, err := ibm.IBMClient.GetSecret(
  340. &sm.GetSecretOptions{
  341. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
  342. ID: &secretName,
  343. })
  344. if err != nil {
  345. return nil, err
  346. }
  347. secret := response.Resources[0].(*sm.SecretResource)
  348. secretData := secret.SecretData.(map[string]interface{})
  349. secretMap := byteArrayMap(secretData)
  350. return secretMap, nil
  351. case sm.CreateSecretOptionsSecretTypeKvConst:
  352. secret, err := getKVSecret(ibm, &secretName, ref)
  353. if err != nil {
  354. return nil, err
  355. }
  356. m := make(map[string]interface{})
  357. err = json.Unmarshal(secret, &m)
  358. if err != nil {
  359. return nil, err
  360. }
  361. secretMap := byteArrayMap(m)
  362. return secretMap, nil
  363. default:
  364. return nil, fmt.Errorf("unknown secret type %s", secretType)
  365. }
  366. }
  367. func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
  368. var err error
  369. secretMap := make(map[string][]byte)
  370. for k, v := range secretData {
  371. secretMap[k], err = getTypedKey(v)
  372. if err != nil {
  373. return nil
  374. }
  375. }
  376. return secretMap
  377. }
  378. // kudos Vault Provider - convert from various types.
  379. func getTypedKey(v interface{}) ([]byte, error) {
  380. switch t := v.(type) {
  381. case string:
  382. return []byte(t), nil
  383. case map[string]interface{}:
  384. return json.Marshal(t)
  385. case map[string]string:
  386. return json.Marshal(t)
  387. case []byte:
  388. return t, nil
  389. // also covers int and float32 due to json.Marshal
  390. case float64:
  391. return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
  392. case bool:
  393. return []byte(strconv.FormatBool(t)), nil
  394. case nil:
  395. return []byte(nil), nil
  396. default:
  397. return nil, fmt.Errorf("secret not in expected format")
  398. }
  399. }
  400. func (ibm *providerIBM) Close(ctx context.Context) error {
  401. return nil
  402. }
  403. func (ibm *providerIBM) Validate() (esv1beta1.ValidationResult, error) {
  404. return esv1beta1.ValidationResultReady, nil
  405. }
  406. func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
  407. storeSpec := store.GetSpec()
  408. ibmSpec := storeSpec.Provider.IBM
  409. if ibmSpec.ServiceURL == nil {
  410. return fmt.Errorf("serviceURL is required")
  411. }
  412. secretRef := ibmSpec.Auth.SecretRef.SecretAPIKey
  413. err := utils.ValidateSecretSelector(store, secretRef)
  414. if err != nil {
  415. return err
  416. }
  417. if secretRef.Name == "" {
  418. return fmt.Errorf("secretAPIKey.name cannot be empty")
  419. }
  420. if secretRef.Key == "" {
  421. return fmt.Errorf("secretAPIKey.key cannot be empty")
  422. }
  423. return nil
  424. }
  425. func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  426. storeSpec := store.GetSpec()
  427. ibmSpec := storeSpec.Provider.IBM
  428. iStore := &client{
  429. kube: kube,
  430. store: ibmSpec,
  431. namespace: namespace,
  432. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  433. }
  434. if err := iStore.setAuth(ctx); err != nil {
  435. return nil, err
  436. }
  437. secretsManager, err := sm.NewSecretsManagerV1(&sm.SecretsManagerV1Options{
  438. URL: *storeSpec.Provider.IBM.ServiceURL,
  439. Authenticator: &core.IamAuthenticator{
  440. ApiKey: string(iStore.credentials),
  441. },
  442. })
  443. // Setup retry options, but only if present
  444. if storeSpec.RetrySettings != nil {
  445. var retryAmount int
  446. var retryDuration time.Duration
  447. if storeSpec.RetrySettings.MaxRetries != nil {
  448. retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
  449. } else {
  450. retryAmount = 3
  451. }
  452. if storeSpec.RetrySettings.RetryInterval != nil {
  453. retryDuration, err = time.ParseDuration(*storeSpec.RetrySettings.RetryInterval)
  454. } else {
  455. retryDuration = 5 * time.Second
  456. }
  457. if err == nil {
  458. secretsManager.Service.EnableRetries(retryAmount, retryDuration)
  459. }
  460. }
  461. if err != nil {
  462. return nil, fmt.Errorf(errIBMClient, err)
  463. }
  464. ibm.IBMClient = secretsManager
  465. return ibm, nil
  466. }
  467. func init() {
  468. esv1beta1.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
  469. IBM: &esv1beta1.IBMProvider{},
  470. })
  471. }