|
|
@@ -3,7 +3,7 @@ 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
|
|
|
+ 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,
|
|
|
@@ -18,16 +18,18 @@ import (
|
|
|
"context"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
- "time"
|
|
|
|
|
|
- kmssdk "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
|
|
|
+ openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
|
|
+ kmssdk "github.com/alibabacloud-go/kms-20160120/v3/client"
|
|
|
+ util "github.com/alibabacloud-go/tea-utils/v2/service"
|
|
|
+ credential "github.com/aliyun/credentials-go/credentials"
|
|
|
+ "github.com/avast/retry-go/v4"
|
|
|
"github.com/tidwall/gjson"
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
|
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
|
- "github.com/external-secrets/external-secrets/pkg/provider/aws/util"
|
|
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
|
|
)
|
|
|
|
|
|
@@ -42,76 +44,18 @@ const (
|
|
|
errMissingAKID = "missing AccessKeyID"
|
|
|
)
|
|
|
|
|
|
-type Client struct {
|
|
|
- kube kclient.Client
|
|
|
- store *esv1beta1.AlibabaProvider
|
|
|
- namespace string
|
|
|
- storeKind string
|
|
|
- regionID string
|
|
|
- keyID []byte
|
|
|
- accessKey []byte
|
|
|
-}
|
|
|
-
|
|
|
// https://github.com/external-secrets/external-secrets/issues/644
|
|
|
var _ esv1beta1.SecretsClient = &KeyManagementService{}
|
|
|
var _ esv1beta1.Provider = &KeyManagementService{}
|
|
|
|
|
|
type KeyManagementService struct {
|
|
|
Client SMInterface
|
|
|
- url string
|
|
|
+ Config *openapi.Config
|
|
|
}
|
|
|
|
|
|
type SMInterface interface {
|
|
|
- GetSecretValue(request *kmssdk.GetSecretValueRequest) (response *kmssdk.GetSecretValueResponse, err error)
|
|
|
-}
|
|
|
-
|
|
|
-// setAuth creates a new Alibaba session based on a store.
|
|
|
-func (c *Client) setAuth(ctx context.Context) error {
|
|
|
- credentialsSecret := &corev1.Secret{}
|
|
|
- credentialsSecretName := c.store.Auth.SecretRef.AccessKeyID.Name
|
|
|
- if credentialsSecretName == "" {
|
|
|
- return fmt.Errorf(errAlibabaCredSecretName)
|
|
|
- }
|
|
|
- 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.AccessKeyID.Namespace == nil {
|
|
|
- return fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
|
|
|
- }
|
|
|
- objectKey.Namespace = *c.store.Auth.SecretRef.AccessKeyID.Namespace
|
|
|
- }
|
|
|
-
|
|
|
- err := c.kube.Get(ctx, objectKey, credentialsSecret)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf(errFetchAKIDSecret, err)
|
|
|
- }
|
|
|
-
|
|
|
- objectKey = types.NamespacedName{
|
|
|
- Name: c.store.Auth.SecretRef.AccessKeySecret.Name,
|
|
|
- Namespace: c.namespace,
|
|
|
- }
|
|
|
- if c.storeKind == esv1beta1.ClusterSecretStoreKind {
|
|
|
- if c.store.Auth.SecretRef.AccessKeySecret.Namespace == nil {
|
|
|
- return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
|
|
|
- }
|
|
|
- objectKey.Namespace = *c.store.Auth.SecretRef.AccessKeySecret.Namespace
|
|
|
- }
|
|
|
- c.keyID = credentialsSecret.Data[c.store.Auth.SecretRef.AccessKeyID.Key]
|
|
|
- fmt.Println(c.keyID)
|
|
|
- fmt.Println(c.accessKey)
|
|
|
- if (c.keyID == nil) || (len(c.keyID) == 0) {
|
|
|
- return fmt.Errorf(errMissingAKID)
|
|
|
- }
|
|
|
- c.accessKey = credentialsSecret.Data[c.store.Auth.SecretRef.AccessKeySecret.Key]
|
|
|
- if (c.accessKey == nil) || (len(c.accessKey) == 0) {
|
|
|
- return fmt.Errorf(errMissingSAK)
|
|
|
- }
|
|
|
- c.regionID = c.store.RegionID
|
|
|
- return nil
|
|
|
+ GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error)
|
|
|
+ Endpoint() string
|
|
|
}
|
|
|
|
|
|
func (kms *KeyManagementService) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
|
|
|
@@ -133,23 +77,28 @@ func (kms *KeyManagementService) GetSecret(ctx context.Context, ref esv1beta1.Ex
|
|
|
if utils.IsNil(kms.Client) {
|
|
|
return nil, fmt.Errorf(errUninitalizedAlibabaProvider)
|
|
|
}
|
|
|
- kmsRequest := kmssdk.CreateGetSecretValueRequest()
|
|
|
- kmsRequest.VersionId = ref.Version
|
|
|
- kmsRequest.SecretName = ref.Key
|
|
|
- kmsRequest.SetScheme("https")
|
|
|
- secretOut, err := kms.Client.GetSecretValue(kmsRequest)
|
|
|
+
|
|
|
+ request := &kmssdk.GetSecretValueRequest{
|
|
|
+ SecretName: &ref.Key,
|
|
|
+ }
|
|
|
+
|
|
|
+ if ref.Version != "" {
|
|
|
+ request.VersionId = &ref.Version
|
|
|
+ }
|
|
|
+
|
|
|
+ secretOut, err := kms.Client.GetSecretValue(ctx, request)
|
|
|
if err != nil {
|
|
|
- return nil, util.SanitizeErr(err)
|
|
|
+ return nil, SanitizeErr(err)
|
|
|
}
|
|
|
if ref.Property == "" {
|
|
|
- if secretOut.SecretData != "" {
|
|
|
- return []byte(secretOut.SecretData), nil
|
|
|
+ if utils.Deref(secretOut.SecretData) != "" {
|
|
|
+ return []byte(utils.Deref(secretOut.SecretData)), nil
|
|
|
}
|
|
|
return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
|
|
|
}
|
|
|
var payload string
|
|
|
- if secretOut.SecretData != "" {
|
|
|
- payload = secretOut.SecretData
|
|
|
+ if utils.Deref(secretOut.SecretData) != "" {
|
|
|
+ payload = utils.Deref(secretOut.SecretData)
|
|
|
}
|
|
|
val := gjson.Get(payload, ref.Property)
|
|
|
if !val.Exists() {
|
|
|
@@ -185,38 +134,170 @@ func (kms *KeyManagementService) Capabilities() esv1beta1.SecretStoreCapabilitie
|
|
|
func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
|
|
storeSpec := store.GetSpec()
|
|
|
alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
- iStore := &Client{
|
|
|
- kube: kube,
|
|
|
- store: alibabaSpec,
|
|
|
- namespace: namespace,
|
|
|
- storeKind: store.GetObjectKind().GroupVersionKind().Kind,
|
|
|
+
|
|
|
+ credentials, err := newAuth(ctx, kube, store, namespace)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to create Alibaba credentials: %w", err)
|
|
|
}
|
|
|
- if err := iStore.setAuth(ctx); err != nil {
|
|
|
- return nil, err
|
|
|
+
|
|
|
+ config := &openapi.Config{
|
|
|
+ RegionId: utils.Ptr(alibabaSpec.RegionID),
|
|
|
+ Credential: credentials,
|
|
|
}
|
|
|
- alibabaRegion := iStore.regionID
|
|
|
- alibabaKeyID := iStore.keyID
|
|
|
- alibabaSecretKey := iStore.accessKey
|
|
|
- keyManagementService, err := kmssdk.NewClientWithAccessKey(alibabaRegion, string(alibabaKeyID), string(alibabaSecretKey))
|
|
|
+
|
|
|
+ options := newOptions(store)
|
|
|
+ client, err := newClient(config, options)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf(errAlibabaClient, err)
|
|
|
}
|
|
|
- kms.Client = keyManagementService
|
|
|
- kms.url = alibabaSpec.Endpoint
|
|
|
+
|
|
|
+ kms.Client = client
|
|
|
+ kms.Config = config
|
|
|
return kms, nil
|
|
|
}
|
|
|
|
|
|
+func newOptions(store esv1beta1.GenericStore) *util.RuntimeOptions {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+
|
|
|
+ options := &util.RuntimeOptions{}
|
|
|
+ // Setup retry options, if present in storeSpec
|
|
|
+ if storeSpec.RetrySettings != nil {
|
|
|
+ var retryAmount int
|
|
|
+
|
|
|
+ if storeSpec.RetrySettings.MaxRetries != nil {
|
|
|
+ retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
|
|
|
+ } else {
|
|
|
+ retryAmount = 3
|
|
|
+ }
|
|
|
+
|
|
|
+ options.Autoretry = utils.Ptr(true)
|
|
|
+ options.MaxAttempts = utils.Ptr(retryAmount)
|
|
|
+ }
|
|
|
+
|
|
|
+ return options
|
|
|
+}
|
|
|
+
|
|
|
+func newAuth(ctx context.Context, kube kclient.Client, store esv1beta1.GenericStore, namespace string) (credential.Credential, error) {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+
|
|
|
+ switch {
|
|
|
+ case alibabaSpec.Auth.RRSAAuth != nil:
|
|
|
+ credentials, err := newRRSAAuth(store)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to create Alibaba OIDC credentials: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return credentials, nil
|
|
|
+ case alibabaSpec.Auth.SecretRef != nil:
|
|
|
+ credentials, err := newAccessKeyAuth(ctx, kube, store, namespace)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to create Alibaba AccessKey credentials: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return credentials, nil
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("alibaba authentication methods wasn't provided")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func newRRSAAuth(store esv1beta1.GenericStore) (credential.Credential, error) {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+
|
|
|
+ credentialConfig := &credential.Config{
|
|
|
+ OIDCProviderArn: &alibabaSpec.Auth.RRSAAuth.OIDCProviderARN,
|
|
|
+ OIDCTokenFilePath: &alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath,
|
|
|
+ RoleArn: &alibabaSpec.Auth.RRSAAuth.RoleARN,
|
|
|
+ RoleSessionName: &alibabaSpec.Auth.RRSAAuth.SessionName,
|
|
|
+ Type: utils.Ptr("oidc_role_arn"),
|
|
|
+ ConnectTimeout: utils.Ptr(30),
|
|
|
+ Timeout: utils.Ptr(60),
|
|
|
+ }
|
|
|
+
|
|
|
+ return credential.NewCredential(credentialConfig)
|
|
|
+}
|
|
|
+
|
|
|
+func newAccessKeyAuth(ctx context.Context, kube kclient.Client, store esv1beta1.GenericStore, namespace string) (credential.Credential, error) {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+ storeKind := store.GetObjectKind().GroupVersionKind().Kind
|
|
|
+
|
|
|
+ credentialsSecret := &corev1.Secret{}
|
|
|
+ credentialsSecretName := alibabaSpec.Auth.SecretRef.AccessKeyID.Name
|
|
|
+ if credentialsSecretName == "" {
|
|
|
+ return nil, fmt.Errorf(errAlibabaCredSecretName)
|
|
|
+ }
|
|
|
+ objectKey := types.NamespacedName{
|
|
|
+ Name: credentialsSecretName,
|
|
|
+ Namespace: namespace,
|
|
|
+ }
|
|
|
+
|
|
|
+ // only ClusterStore is allowed to set namespace (and then it's required)
|
|
|
+ if storeKind == esv1beta1.ClusterSecretStoreKind {
|
|
|
+ if alibabaSpec.Auth.SecretRef.AccessKeyID.Namespace == nil {
|
|
|
+ return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
|
|
|
+ }
|
|
|
+ objectKey.Namespace = *alibabaSpec.Auth.SecretRef.AccessKeyID.Namespace
|
|
|
+ }
|
|
|
+
|
|
|
+ err := kube.Get(ctx, objectKey, credentialsSecret)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf(errFetchAKIDSecret, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ objectKey = types.NamespacedName{
|
|
|
+ Name: alibabaSpec.Auth.SecretRef.AccessKeySecret.Name,
|
|
|
+ Namespace: namespace,
|
|
|
+ }
|
|
|
+ if storeKind == esv1beta1.ClusterSecretStoreKind {
|
|
|
+ if alibabaSpec.Auth.SecretRef.AccessKeySecret.Namespace == nil {
|
|
|
+ return nil, fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
|
|
|
+ }
|
|
|
+ objectKey.Namespace = *alibabaSpec.Auth.SecretRef.AccessKeySecret.Namespace
|
|
|
+ }
|
|
|
+
|
|
|
+ accessKeyID := credentialsSecret.Data[alibabaSpec.Auth.SecretRef.AccessKeyID.Key]
|
|
|
+ if (accessKeyID == nil) || (len(accessKeyID) == 0) {
|
|
|
+ return nil, fmt.Errorf(errMissingAKID)
|
|
|
+ }
|
|
|
+
|
|
|
+ accessKeySecret := credentialsSecret.Data[alibabaSpec.Auth.SecretRef.AccessKeySecret.Key]
|
|
|
+ if (accessKeySecret == nil) || (len(accessKeySecret) == 0) {
|
|
|
+ return nil, fmt.Errorf(errMissingSAK)
|
|
|
+ }
|
|
|
+
|
|
|
+ credentialConfig := &credential.Config{
|
|
|
+ AccessKeyId: utils.Ptr(string(accessKeyID)),
|
|
|
+ AccessKeySecret: utils.Ptr(string(accessKeySecret)),
|
|
|
+ Type: utils.Ptr("access_key"),
|
|
|
+ ConnectTimeout: utils.Ptr(30),
|
|
|
+ Timeout: utils.Ptr(60),
|
|
|
+ }
|
|
|
+
|
|
|
+ return credential.NewCredential(credentialConfig)
|
|
|
+}
|
|
|
+
|
|
|
func (kms *KeyManagementService) Close(ctx context.Context) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (kms *KeyManagementService) Validate() (esv1beta1.ValidationResult, error) {
|
|
|
- timeout := 15 * time.Second
|
|
|
- url := kms.url
|
|
|
-
|
|
|
- if err := utils.NetworkValidate(url, timeout); err != nil {
|
|
|
- return esv1beta1.ValidationResultError, err
|
|
|
+ err := retry.Do(
|
|
|
+ func() error {
|
|
|
+ _, err := kms.Config.Credential.GetSecurityToken()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+ },
|
|
|
+ retry.Attempts(5),
|
|
|
+ )
|
|
|
+ if err != nil {
|
|
|
+ return esv1beta1.ValidationResultError, SanitizeErr(err)
|
|
|
}
|
|
|
+
|
|
|
return esv1beta1.ValidationResultReady, nil
|
|
|
}
|
|
|
|
|
|
@@ -230,6 +311,50 @@ func (kms *KeyManagementService) ValidateStore(store esv1beta1.GenericStore) err
|
|
|
return fmt.Errorf("missing alibaba region")
|
|
|
}
|
|
|
|
|
|
+ return kms.validateStoreAuth(store)
|
|
|
+}
|
|
|
+
|
|
|
+func (kms *KeyManagementService) validateStoreAuth(store esv1beta1.GenericStore) error {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+
|
|
|
+ switch {
|
|
|
+ case alibabaSpec.Auth.RRSAAuth != nil:
|
|
|
+ return kms.validateStoreRRSAAuth(store)
|
|
|
+ case alibabaSpec.Auth.SecretRef != nil:
|
|
|
+ return kms.validateStoreAccessKeyAuth(store)
|
|
|
+ default:
|
|
|
+ return fmt.Errorf("missing alibaba auth provider")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (kms *KeyManagementService) validateStoreRRSAAuth(store esv1beta1.GenericStore) error {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+
|
|
|
+ if alibabaSpec.Auth.RRSAAuth.OIDCProviderARN == "" {
|
|
|
+ return fmt.Errorf("missing alibaba OIDC proivder ARN")
|
|
|
+ }
|
|
|
+
|
|
|
+ if alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath == "" {
|
|
|
+ return fmt.Errorf("missing alibaba OIDC token file path")
|
|
|
+ }
|
|
|
+
|
|
|
+ if alibabaSpec.Auth.RRSAAuth.RoleARN == "" {
|
|
|
+ return fmt.Errorf("missing alibaba Assume Role ARN")
|
|
|
+ }
|
|
|
+
|
|
|
+ if alibabaSpec.Auth.RRSAAuth.SessionName == "" {
|
|
|
+ return fmt.Errorf("missing alibaba session name")
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (kms *KeyManagementService) validateStoreAccessKeyAuth(store esv1beta1.GenericStore) error {
|
|
|
+ storeSpec := store.GetSpec()
|
|
|
+ alibabaSpec := storeSpec.Provider.Alibaba
|
|
|
+
|
|
|
accessKeyID := alibabaSpec.Auth.SecretRef.AccessKeyID
|
|
|
err := utils.ValidateSecretSelector(store, accessKeyID)
|
|
|
if err != nil {
|