provider.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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. "strings"
  18. "time"
  19. "github.com/IBM/go-sdk-core/v5/core"
  20. sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
  21. corev1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  25. "github.com/external-secrets/external-secrets/pkg/utils"
  26. )
  27. const (
  28. SecretsManagerEndpointEnv = "IBM_SECRETSMANAGER_ENDPOINT"
  29. STSEndpointEnv = "IBM_STS_ENDPOINT"
  30. SSMEndpointEnv = "IBM_SSM_ENDPOINT"
  31. errIBMClient = "cannot setup new ibm client: %w"
  32. errIBMCredSecretName = "invalid IBM SecretStore resource: missing IBM APIKey"
  33. errUninitalizedIBMProvider = "provider IBM is not initialized"
  34. errInvalidClusterStoreMissingSKNamespace = "invalid ClusterStore, missing namespace"
  35. errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
  36. errMissingSAK = "missing SecretAccessKey"
  37. errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
  38. )
  39. type SecretManagerClient interface {
  40. GetSecret(getSecretOptions *sm.GetSecretOptions) (result *sm.GetSecret, response *core.DetailedResponse, err error)
  41. }
  42. type providerIBM struct {
  43. IBMClient SecretManagerClient
  44. }
  45. type client struct {
  46. kube kclient.Client
  47. store *esv1beta1.IBMProvider
  48. namespace string
  49. storeKind string
  50. credentials []byte
  51. }
  52. func (c *client) setAuth(ctx context.Context) error {
  53. credentialsSecret := &corev1.Secret{}
  54. credentialsSecretName := c.store.Auth.SecretRef.SecretAPIKey.Name
  55. if credentialsSecretName == "" {
  56. return fmt.Errorf(errIBMCredSecretName)
  57. }
  58. objectKey := types.NamespacedName{
  59. Name: credentialsSecretName,
  60. Namespace: c.namespace,
  61. }
  62. // only ClusterStore is allowed to set namespace (and then it's required)
  63. if c.storeKind == esv1beta1.ClusterSecretStoreKind {
  64. if c.store.Auth.SecretRef.SecretAPIKey.Namespace == nil {
  65. return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
  66. }
  67. objectKey.Namespace = *c.store.Auth.SecretRef.SecretAPIKey.Namespace
  68. }
  69. err := c.kube.Get(ctx, objectKey, credentialsSecret)
  70. if err != nil {
  71. return fmt.Errorf(errFetchSAKSecret, err)
  72. }
  73. c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAPIKey.Key]
  74. if (c.credentials == nil) || (len(c.credentials) == 0) {
  75. return fmt.Errorf(errMissingSAK)
  76. }
  77. return nil
  78. }
  79. // Empty GetAllSecrets.
  80. func (ibm *providerIBM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  81. // TO be implemented
  82. return nil, fmt.Errorf("GetAllSecrets not implemented")
  83. }
  84. func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  85. if utils.IsNil(ibm.IBMClient) {
  86. return nil, fmt.Errorf(errUninitalizedIBMProvider)
  87. }
  88. secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
  89. secretName := ref.Key
  90. nameSplitted := strings.Split(secretName, "/")
  91. if len(nameSplitted) > 1 {
  92. secretType = nameSplitted[0]
  93. secretName = nameSplitted[1]
  94. }
  95. switch secretType {
  96. case sm.GetSecretOptionsSecretTypeArbitraryConst:
  97. return getArbitrarySecret(ibm, &secretName)
  98. case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
  99. if ref.Property == "" {
  100. return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
  101. }
  102. return getUsernamePasswordSecret(ibm, &secretName, ref)
  103. case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
  104. return getIamCredentialsSecret(ibm, &secretName)
  105. case sm.CreateSecretOptionsSecretTypeImportedCertConst:
  106. if ref.Property == "" {
  107. return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
  108. }
  109. return getImportCertSecret(ibm, &secretName, ref)
  110. default:
  111. return nil, fmt.Errorf("unknown secret type %s", secretType)
  112. }
  113. }
  114. func getArbitrarySecret(ibm *providerIBM, secretName *string) ([]byte, error) {
  115. response, _, err := ibm.IBMClient.GetSecret(
  116. &sm.GetSecretOptions{
  117. SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
  118. ID: secretName,
  119. })
  120. if err != nil {
  121. return nil, err
  122. }
  123. secret := response.Resources[0].(*sm.SecretResource)
  124. secretData := secret.SecretData.(map[string]interface{})
  125. arbitrarySecretPayload := secretData["payload"].(string)
  126. return []byte(arbitrarySecretPayload), nil
  127. }
  128. func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  129. response, _, err := ibm.IBMClient.GetSecret(
  130. &sm.GetSecretOptions{
  131. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  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. if val, ok := secretData[ref.Property]; ok {
  140. return []byte(val.(string)), nil
  141. }
  142. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  143. }
  144. func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
  145. response, _, err := ibm.IBMClient.GetSecret(
  146. &sm.GetSecretOptions{
  147. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  148. ID: secretName,
  149. })
  150. if err != nil {
  151. return nil, err
  152. }
  153. secret := response.Resources[0].(*sm.SecretResource)
  154. secretData := *secret.APIKey
  155. return []byte(secretData), nil
  156. }
  157. func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  158. response, _, err := ibm.IBMClient.GetSecret(
  159. &sm.GetSecretOptions{
  160. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  161. ID: secretName,
  162. })
  163. if err != nil {
  164. return nil, err
  165. }
  166. secret := response.Resources[0].(*sm.SecretResource)
  167. secretData := secret.SecretData.(map[string]interface{})
  168. if val, ok := secretData[ref.Property]; ok {
  169. return []byte(val.(string)), nil
  170. }
  171. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  172. }
  173. func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  174. if utils.IsNil(ibm.IBMClient) {
  175. return nil, fmt.Errorf(errUninitalizedIBMProvider)
  176. }
  177. secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
  178. secretName := ref.Key
  179. nameSplitted := strings.Split(secretName, "/")
  180. if len(nameSplitted) > 1 {
  181. secretType = nameSplitted[0]
  182. secretName = nameSplitted[1]
  183. }
  184. switch secretType {
  185. case sm.GetSecretOptionsSecretTypeArbitraryConst:
  186. response, _, err := ibm.IBMClient.GetSecret(
  187. &sm.GetSecretOptions{
  188. SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
  189. ID: &ref.Key,
  190. })
  191. if err != nil {
  192. return nil, err
  193. }
  194. secret := response.Resources[0].(*sm.SecretResource)
  195. secretData := secret.SecretData.(map[string]interface{})
  196. arbitrarySecretPayload := secretData["payload"].(string)
  197. kv := make(map[string]interface{})
  198. err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
  199. if err != nil {
  200. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  201. }
  202. secretMap := byteArrayMap(kv)
  203. return secretMap, nil
  204. case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
  205. response, _, err := ibm.IBMClient.GetSecret(
  206. &sm.GetSecretOptions{
  207. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  208. ID: &secretName,
  209. })
  210. if err != nil {
  211. return nil, err
  212. }
  213. secret := response.Resources[0].(*sm.SecretResource)
  214. secretData := secret.SecretData.(map[string]interface{})
  215. secretMap := byteArrayMap(secretData)
  216. return secretMap, nil
  217. case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
  218. response, _, err := ibm.IBMClient.GetSecret(
  219. &sm.GetSecretOptions{
  220. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  221. ID: &secretName,
  222. })
  223. if err != nil {
  224. return nil, err
  225. }
  226. secret := response.Resources[0].(*sm.SecretResource)
  227. secretData := *secret.APIKey
  228. secretMap := make(map[string][]byte)
  229. secretMap["apikey"] = []byte(secretData)
  230. return secretMap, nil
  231. case sm.CreateSecretOptionsSecretTypeImportedCertConst:
  232. response, _, err := ibm.IBMClient.GetSecret(
  233. &sm.GetSecretOptions{
  234. SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  235. ID: &secretName,
  236. })
  237. if err != nil {
  238. return nil, err
  239. }
  240. secret := response.Resources[0].(*sm.SecretResource)
  241. secretData := secret.SecretData.(map[string]interface{})
  242. secretMap := byteArrayMap(secretData)
  243. return secretMap, nil
  244. default:
  245. return nil, fmt.Errorf("unknown secret type %s", secretType)
  246. }
  247. }
  248. func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
  249. secretMap := make(map[string][]byte)
  250. for k, v := range secretData {
  251. secretMap[k] = []byte(v.(string))
  252. }
  253. return secretMap
  254. }
  255. func (ibm *providerIBM) Close(ctx context.Context) error {
  256. return nil
  257. }
  258. func (ibm *providerIBM) Validate() error {
  259. return nil
  260. }
  261. func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
  262. storeSpec := store.GetSpec()
  263. ibmSpec := storeSpec.Provider.IBM
  264. if ibmSpec.ServiceURL == nil {
  265. return fmt.Errorf("serviceURL is required")
  266. }
  267. secretRef := ibmSpec.Auth.SecretRef.SecretAPIKey
  268. err := utils.ValidateSecretSelector(store, secretRef)
  269. if err != nil {
  270. return err
  271. }
  272. if secretRef.Name == "" {
  273. return fmt.Errorf("secretAPIKey.name cannot be empty")
  274. }
  275. if secretRef.Key == "" {
  276. return fmt.Errorf("secretAPIKey.key cannot be empty")
  277. }
  278. return nil
  279. }
  280. func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  281. storeSpec := store.GetSpec()
  282. ibmSpec := storeSpec.Provider.IBM
  283. iStore := &client{
  284. kube: kube,
  285. store: ibmSpec,
  286. namespace: namespace,
  287. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  288. }
  289. if err := iStore.setAuth(ctx); err != nil {
  290. return nil, err
  291. }
  292. secretsManager, err := sm.NewSecretsManagerV1(&sm.SecretsManagerV1Options{
  293. URL: *storeSpec.Provider.IBM.ServiceURL,
  294. Authenticator: &core.IamAuthenticator{
  295. ApiKey: string(iStore.credentials),
  296. },
  297. })
  298. // Setup retry options, but only if present
  299. if storeSpec.RetrySettings != nil {
  300. var retryAmount int
  301. var retryDuration time.Duration
  302. if storeSpec.RetrySettings.MaxRetries != nil {
  303. retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
  304. } else {
  305. retryAmount = 3
  306. }
  307. if storeSpec.RetrySettings.RetryInterval != nil {
  308. retryDuration, err = time.ParseDuration(*storeSpec.RetrySettings.RetryInterval)
  309. } else {
  310. retryDuration = 5 * time.Second
  311. }
  312. if err == nil {
  313. secretsManager.Service.EnableRetries(retryAmount, retryDuration)
  314. }
  315. }
  316. if err != nil {
  317. return nil, fmt.Errorf(errIBMClient, err)
  318. }
  319. ibm.IBMClient = secretsManager
  320. return ibm, nil
  321. }
  322. func init() {
  323. esv1beta1.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
  324. IBM: &esv1beta1.IBMProvider{},
  325. })
  326. }