| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package ibm
- import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "strconv"
- "strings"
- "time"
- core "github.com/IBM/go-sdk-core/v5/core"
- sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
- gjson "github.com/tidwall/gjson"
- corev1 "k8s.io/api/core/v1"
- types "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
- esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
- utils "github.com/external-secrets/external-secrets/pkg/utils"
- )
- const (
- SecretsManagerEndpointEnv = "IBM_SECRETSMANAGER_ENDPOINT"
- STSEndpointEnv = "IBM_STS_ENDPOINT"
- SSMEndpointEnv = "IBM_SSM_ENDPOINT"
- errIBMClient = "cannot setup new ibm client: %w"
- errIBMCredSecretName = "invalid IBM SecretStore resource: missing IBM APIKey"
- errUninitalizedIBMProvider = "provider IBM is not initialized"
- errInvalidClusterStoreMissingSKNamespace = "invalid ClusterStore, missing namespace"
- errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
- errMissingSAK = "missing SecretAccessKey"
- errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
- )
- // https://github.com/external-secrets/external-secrets/issues/644
- var _ esv1beta1.SecretsClient = &providerIBM{}
- var _ esv1beta1.Provider = &providerIBM{}
- type SecretManagerClient interface {
- GetSecret(getSecretOptions *sm.GetSecretOptions) (result *sm.GetSecret, response *core.DetailedResponse, err error)
- }
- type providerIBM struct {
- IBMClient SecretManagerClient
- }
- type client struct {
- kube kclient.Client
- store *esv1beta1.IBMProvider
- namespace string
- storeKind string
- credentials []byte
- }
- var log = ctrl.Log.WithName("provider").WithName("ibm").WithName("secretsmanager")
- func (c *client) setAuth(ctx context.Context) error {
- credentialsSecret := &corev1.Secret{}
- credentialsSecretName := c.store.Auth.SecretRef.SecretAPIKey.Name
- if credentialsSecretName == "" {
- return fmt.Errorf(errIBMCredSecretName)
- }
- objectKey := types.NamespacedName{
- Name: credentialsSecretName,
- Namespace: c.namespace,
- }
- // only ClusterStore is allowed to set namespace (and then it's required)
- if c.storeKind == esv1beta1.ClusterSecretStoreKind {
- if c.store.Auth.SecretRef.SecretAPIKey.Namespace == nil {
- return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
- }
- objectKey.Namespace = *c.store.Auth.SecretRef.SecretAPIKey.Namespace
- }
- err := c.kube.Get(ctx, objectKey, credentialsSecret)
- if err != nil {
- return fmt.Errorf(errFetchSAKSecret, err)
- }
- c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAPIKey.Key]
- if (c.credentials == nil) || (len(c.credentials) == 0) {
- return fmt.Errorf(errMissingSAK)
- }
- return nil
- }
- func (ibm *providerIBM) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
- return fmt.Errorf("not implemented")
- }
- // Not Implemented PushSecret.
- func (ibm *providerIBM) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
- return fmt.Errorf("not implemented")
- }
- // Empty GetAllSecrets.
- func (ibm *providerIBM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
- // TO be implemented
- return nil, fmt.Errorf("GetAllSecrets not implemented")
- }
- func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- if utils.IsNil(ibm.IBMClient) {
- return nil, fmt.Errorf(errUninitalizedIBMProvider)
- }
- secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
- secretName := ref.Key
- nameSplitted := strings.Split(secretName, "/")
- if len(nameSplitted) > 1 {
- secretType = nameSplitted[0]
- secretName = nameSplitted[1]
- }
- switch secretType {
- case sm.GetSecretOptionsSecretTypeArbitraryConst:
- return getArbitrarySecret(ibm, &secretName)
- case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
- if ref.Property == "" {
- return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
- }
- return getUsernamePasswordSecret(ibm, &secretName, ref)
- case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
- return getIamCredentialsSecret(ibm, &secretName)
- case sm.CreateSecretOptionsSecretTypeImportedCertConst:
- if ref.Property == "" {
- return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
- }
- return getImportCertSecret(ibm, &secretName, ref)
- case sm.CreateSecretOptionsSecretTypePublicCertConst:
- if ref.Property == "" {
- return nil, fmt.Errorf("remoteRef.property required for secret type public_cert")
- }
- return getPublicCertSecret(ibm, &secretName, ref)
- case sm.CreateSecretOptionsSecretTypePrivateCertConst:
- if ref.Property == "" {
- return nil, fmt.Errorf("remoteRef.property required for secret type private_cert")
- }
- return getPrivateCertSecret(ibm, &secretName, ref)
- case sm.CreateSecretOptionsSecretTypeKvConst:
- return getKVSecret(ibm, &secretName, ref)
- default:
- return nil, fmt.Errorf("unknown secret type %s", secretType)
- }
- }
- func getArbitrarySecret(ibm *providerIBM, secretName *string) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- arbitrarySecretPayload := secretData["payload"].(string)
- return []byte(arbitrarySecretPayload), nil
- }
- func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- if val, ok := secretData[ref.Property]; ok {
- return []byte(val.(string)), nil
- }
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- func getPublicCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- if val, ok := secretData[ref.Property]; ok {
- return []byte(val.(string)), nil
- }
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- func getPrivateCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePrivateCertConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- if val, ok := secretData[ref.Property]; ok {
- return []byte(val.(string)), nil
- }
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := *secret.APIKey
- return []byte(secretData), nil
- }
- func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- if val, ok := secretData[ref.Property]; ok {
- return []byte(val.(string)), nil
- }
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- // Returns a secret of type kv and supports json path.
- func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- secret, err := getSecretByType(ibm, secretName, sm.CreateSecretOptionsSecretTypeKvConst)
- if err != nil {
- return nil, err
- }
- log.Info("fetching secret", "secretName", secretName, "key", ref.Key)
- secretData := secret.SecretData
- payload, ok := secretData["payload"]
- if !ok {
- return nil, fmt.Errorf("no payload returned for secret %s", ref.Key)
- }
- payloadJSON := payload
- payloadJSONMap, ok := payloadJSON.(map[string]interface{})
- if ok {
- var payloadJSONByte []byte
- payloadJSONByte, err = json.Marshal(payloadJSONMap)
- if err != nil {
- return nil, fmt.Errorf("marshaling payload from secret failed. %w", err)
- }
- payloadJSON = string(payloadJSONByte)
- }
- // no property requested, return the entire payload
- if ref.Property == "" {
- return []byte(payloadJSON.(string)), nil
- }
- // returns the requested key
- // consider that the key contains a ".". this could be one of 2 options
- // a) "." is part of the key name
- // b) "." is symbole for JSON path
- if ref.Property != "" {
- refProperty := ref.Property
- // a) "." is part the key name
- // escape "."
- idx := strings.Index(refProperty, ".")
- if idx > 0 {
- refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
- val := gjson.Get(payloadJSON.(string), refProperty)
- if val.Exists() {
- return []byte(val.String()), nil
- }
- }
- // b) "." is symbole for JSON path
- // try to get value for this path
- val := gjson.Get(payloadJSON.(string), ref.Property)
- if !val.Exists() {
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- return []byte(val.String()), nil
- }
- return nil, fmt.Errorf("no property provided for secret %s", ref.Key)
- }
- func getSecretByType(ibm *providerIBM, secretName *string, secretType string) (*sm.SecretResource, error) {
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(secretType),
- ID: secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- return secret, nil
- }
- func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
- if utils.IsNil(ibm.IBMClient) {
- return nil, fmt.Errorf(errUninitalizedIBMProvider)
- }
- secretType := sm.GetSecretOptionsSecretTypeArbitraryConst
- secretName := ref.Key
- nameSplitted := strings.Split(secretName, "/")
- if len(nameSplitted) > 1 {
- secretType = nameSplitted[0]
- secretName = nameSplitted[1]
- }
- switch secretType {
- case sm.GetSecretOptionsSecretTypeArbitraryConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
- ID: &ref.Key,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- arbitrarySecretPayload := secretData["payload"].(string)
- kv := make(map[string]interface{})
- err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
- if err != nil {
- return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
- }
- secretMap := byteArrayMap(kv)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
- ID: &secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- secretMap := byteArrayMap(secretData)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
- ID: &secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := *secret.APIKey
- secretMap := make(map[string][]byte)
- secretMap["apikey"] = []byte(secretData)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypeImportedCertConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
- ID: &secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- secretMap := byteArrayMap(secretData)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypePublicCertConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
- ID: &secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- secretMap := byteArrayMap(secretData)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypePrivateCertConst:
- response, _, err := ibm.IBMClient.GetSecret(
- &sm.GetSecretOptions{
- SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePrivateCertConst),
- ID: &secretName,
- })
- if err != nil {
- return nil, err
- }
- secret := response.Resources[0].(*sm.SecretResource)
- secretData := secret.SecretData
- secretMap := byteArrayMap(secretData)
- return secretMap, nil
- case sm.CreateSecretOptionsSecretTypeKvConst:
- secret, err := getKVSecret(ibm, &secretName, ref)
- if err != nil {
- return nil, err
- }
- m := make(map[string]interface{})
- err = json.Unmarshal(secret, &m)
- if err != nil {
- return nil, err
- }
- secretMap := byteArrayMap(m)
- return secretMap, nil
- default:
- return nil, fmt.Errorf("unknown secret type %s", secretType)
- }
- }
- func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
- var err error
- secretMap := make(map[string][]byte)
- for k, v := range secretData {
- secretMap[k], err = getTypedKey(v)
- if err != nil {
- return nil
- }
- }
- return secretMap
- }
- // kudos Vault Provider - convert from various types.
- func getTypedKey(v interface{}) ([]byte, error) {
- switch t := v.(type) {
- case string:
- return []byte(t), nil
- case map[string]interface{}:
- return json.Marshal(t)
- case map[string]string:
- return json.Marshal(t)
- case []byte:
- return t, nil
- // also covers int and float32 due to json.Marshal
- case float64:
- return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
- case bool:
- return []byte(strconv.FormatBool(t)), nil
- case nil:
- return []byte(nil), nil
- default:
- return nil, fmt.Errorf("secret not in expected format")
- }
- }
- func (ibm *providerIBM) Close(ctx context.Context) error {
- return nil
- }
- func (ibm *providerIBM) Validate() (esv1beta1.ValidationResult, error) {
- return esv1beta1.ValidationResultReady, nil
- }
- func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
- storeSpec := store.GetSpec()
- ibmSpec := storeSpec.Provider.IBM
- if ibmSpec.ServiceURL == nil {
- return fmt.Errorf("serviceURL is required")
- }
- containerRef := ibmSpec.Auth.ContainerAuth
- secretKeyRef := ibmSpec.Auth.SecretRef.SecretAPIKey
- if utils.IsNil(containerRef.Profile) || (containerRef.Profile == "") {
- // proceed with API Key Auth validation
- err := utils.ValidateSecretSelector(store, secretKeyRef)
- if err != nil {
- return err
- }
- if secretKeyRef.Name == "" {
- return fmt.Errorf("secretAPIKey.name cannot be empty")
- }
- if secretKeyRef.Key == "" {
- return fmt.Errorf("secretAPIKey.key cannot be empty")
- }
- } else {
- // proceed with container auth
- if containerRef.TokenLocation == "" {
- containerRef.TokenLocation = "/var/run/secrets/tokens/vault-token"
- }
- if _, err := os.Open(containerRef.TokenLocation); err != nil {
- return fmt.Errorf("cannot read container auth token %s. %w", containerRef.TokenLocation, err)
- }
- }
- return nil
- }
- // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
- func (ibm *providerIBM) Capabilities() esv1beta1.SecretStoreCapabilities {
- return esv1beta1.SecretStoreReadOnly
- }
- func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
- storeSpec := store.GetSpec()
- ibmSpec := storeSpec.Provider.IBM
- iStore := &client{
- kube: kube,
- store: ibmSpec,
- namespace: namespace,
- storeKind: store.GetObjectKind().GroupVersionKind().Kind,
- }
- var err error
- var secretsManager *sm.SecretsManagerV1
- containerAuthProfile := iStore.store.Auth.ContainerAuth.Profile
- if containerAuthProfile != "" {
- // container-based auth
- containerAuthToken := iStore.store.Auth.ContainerAuth.TokenLocation
- containerAuthEndpoint := iStore.store.Auth.ContainerAuth.IAMEndpoint
- if containerAuthToken == "" {
- // API default path
- containerAuthToken = "/var/run/secrets/tokens/vault-token"
- }
- if containerAuthEndpoint == "" {
- // API default path
- containerAuthEndpoint = "https://iam.cloud.ibm.com"
- }
- authenticator, err := core.NewContainerAuthenticatorBuilder().
- SetIAMProfileName(containerAuthProfile).
- SetCRTokenFilename(containerAuthToken).
- SetURL(containerAuthEndpoint).
- Build()
- if err != nil {
- return nil, fmt.Errorf(errIBMClient, err)
- }
- secretsManager, err = sm.NewSecretsManagerV1(&sm.SecretsManagerV1Options{
- URL: *storeSpec.Provider.IBM.ServiceURL,
- Authenticator: authenticator,
- })
- if err != nil {
- return nil, fmt.Errorf(errIBMClient, err)
- }
- } else {
- // API Key-based auth
- if err := iStore.setAuth(ctx); err != nil {
- return nil, err
- }
- secretsManager, err = sm.NewSecretsManagerV1(&sm.SecretsManagerV1Options{
- URL: *storeSpec.Provider.IBM.ServiceURL,
- Authenticator: &core.IamAuthenticator{
- ApiKey: string(iStore.credentials),
- },
- })
- }
- // Setup retry options, but only if present
- if storeSpec.RetrySettings != nil {
- var retryAmount int
- var retryDuration time.Duration
- if storeSpec.RetrySettings.MaxRetries != nil {
- retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
- } else {
- retryAmount = 3
- }
- if storeSpec.RetrySettings.RetryInterval != nil {
- retryDuration, err = time.ParseDuration(*storeSpec.RetrySettings.RetryInterval)
- } else {
- retryDuration = 5 * time.Second
- }
- if err == nil {
- secretsManager.Service.EnableRetries(retryAmount, retryDuration)
- }
- }
- if err != nil {
- return nil, fmt.Errorf(errIBMClient, err)
- }
- ibm.IBMClient = secretsManager
- return ibm, nil
- }
- func init() {
- esv1beta1.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
- IBM: &esv1beta1.IBMProvider{},
- })
- }
|