provider.go 10 KB

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