mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-15 17:51:01 +00:00
468 lines
13 KiB
Go
468 lines
13 KiB
Go
/*
|
|
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"
|
|
"strings"
|
|
"time"
|
|
|
|
core "github.com/IBM/go-sdk-core/v5/core"
|
|
sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
|
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
utils "github.com/external-secrets/external-secrets/pkg/utils"
|
|
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"
|
|
)
|
|
|
|
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"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// 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.CreateSecretOptionsSecretTypeKvConst:
|
|
|
|
if ref.Property == "" {
|
|
return nil, fmt.Errorf("remoteRef.property required for secret type kv")
|
|
}
|
|
|
|
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.(map[string]interface{})
|
|
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.(map[string]interface{})
|
|
|
|
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.(map[string]interface{})
|
|
|
|
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("getKVSecret", "secretName", secretName)
|
|
secretData := secret.SecretData.(map[string]interface{})
|
|
|
|
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)
|
|
}
|
|
|
|
if ref.Property != "" {
|
|
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.(map[string]interface{})
|
|
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.(map[string]interface{})
|
|
|
|
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.(map[string]interface{})
|
|
|
|
secretMap := byteArrayMap(secretData)
|
|
|
|
return secretMap, nil
|
|
|
|
case sm.CreateSecretOptionsSecretTypeKvConst:
|
|
secret, err := getSecretByType(ibm, &secretName, sm.CreateSecretOptionsSecretTypeKvConst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
secretData := secret.SecretData.(map[string]interface{})
|
|
secretPayload := secretData["payload"].(string)
|
|
|
|
kv := make(map[string]interface{})
|
|
err = json.Unmarshal([]byte(secretPayload), &kv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
|
|
}
|
|
|
|
secretMap := byteArrayMap(kv)
|
|
|
|
return secretMap, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown secret type %s", secretType)
|
|
}
|
|
}
|
|
|
|
func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
|
|
secretMap := make(map[string][]byte)
|
|
for k, v := range secretData {
|
|
secretMap[k] = []byte(v.(string))
|
|
}
|
|
return secretMap
|
|
}
|
|
|
|
func (ibm *providerIBM) Close(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (ibm *providerIBM) Validate() error {
|
|
return nil
|
|
}
|
|
|
|
func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
|
|
return nil
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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{},
|
|
})
|
|
}
|