kms.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package alibaba
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
  20. kmssdk "github.com/alibabacloud-go/kms-20160120/v3/client"
  21. util "github.com/alibabacloud-go/tea-utils/v2/service"
  22. credential "github.com/aliyun/credentials-go/credentials"
  23. "github.com/avast/retry-go/v4"
  24. "github.com/tidwall/gjson"
  25. corev1 "k8s.io/api/core/v1"
  26. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  27. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  28. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. "github.com/external-secrets/external-secrets/runtime/esutils"
  30. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  31. )
  32. const (
  33. errAlibabaClient = "cannot setup new Alibaba client: %w"
  34. errUninitalizedAlibabaProvider = "provider Alibaba is not initialized"
  35. errFetchAccessKeyID = "could not fetch AccessKeyID secret: %w"
  36. errFetchAccessKeySecret = "could not fetch AccessKeySecret secret: %w"
  37. errNotImplemented = "not implemented"
  38. )
  39. // https://github.com/external-secrets/external-secrets/issues/644
  40. var _ esv1.SecretsClient = &KeyManagementService{}
  41. var _ esv1.Provider = &KeyManagementService{}
  42. // KeyManagementService implements the Alibaba KMS provider for External Secrets.
  43. type KeyManagementService struct {
  44. Client SMInterface
  45. Config *openapi.Config
  46. }
  47. // SMInterface defines the interface for interacting with the Alibaba Secrets Manager.
  48. type SMInterface interface {
  49. GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error)
  50. Endpoint() string
  51. }
  52. // PushSecret implements the SecretsClient PushSecret interface for Alibaba Cloud KMS.
  53. func (kms *KeyManagementService) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
  54. return errors.New(errNotImplemented)
  55. }
  56. // DeleteSecret implements the SecretsClient DeleteSecret interface for Alibaba Cloud KMS.
  57. func (kms *KeyManagementService) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  58. return errors.New(errNotImplemented)
  59. }
  60. // SecretExists implements the SecretsClient SecretExists interface for Alibaba Cloud KMS.
  61. func (kms *KeyManagementService) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  62. return false, errors.New(errNotImplemented)
  63. }
  64. // GetAllSecrets returns all secrets from the provider.
  65. func (kms *KeyManagementService) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
  66. // TO be implemented
  67. return nil, errors.New(errNotImplemented)
  68. }
  69. // GetSecret returns a single secret from the provider.
  70. func (kms *KeyManagementService) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  71. if esutils.IsNil(kms.Client) {
  72. return nil, errors.New(errUninitalizedAlibabaProvider)
  73. }
  74. request := &kmssdk.GetSecretValueRequest{
  75. SecretName: &ref.Key,
  76. }
  77. if ref.Version != "" {
  78. request.VersionId = &ref.Version
  79. }
  80. secretOut, err := kms.Client.GetSecretValue(ctx, request)
  81. if err != nil {
  82. return nil, SanitizeErr(err)
  83. }
  84. if ref.Property == "" {
  85. if esutils.Deref(secretOut.SecretData) != "" {
  86. return []byte(esutils.Deref(secretOut.SecretData)), nil
  87. }
  88. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  89. }
  90. var payload string
  91. if esutils.Deref(secretOut.SecretData) != "" {
  92. payload = esutils.Deref(secretOut.SecretData)
  93. }
  94. val := gjson.Get(payload, ref.Property)
  95. if !val.Exists() {
  96. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  97. }
  98. return []byte(val.String()), nil
  99. }
  100. // GetSecretMap returns multiple k/v pairs from the provider.
  101. func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  102. data, err := kms.GetSecret(ctx, ref)
  103. if err != nil {
  104. return nil, err
  105. }
  106. kv := make(map[string]string)
  107. err = json.Unmarshal(data, &kv)
  108. if err != nil {
  109. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  110. }
  111. secretData := make(map[string][]byte)
  112. for k, v := range kv {
  113. secretData[k] = []byte(v)
  114. }
  115. return secretData, nil
  116. }
  117. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  118. func (kms *KeyManagementService) Capabilities() esv1.SecretStoreCapabilities {
  119. return esv1.SecretStoreReadOnly
  120. }
  121. // NewClient constructs a new secrets client based on the provided store.
  122. func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  123. storeSpec := store.GetSpec()
  124. alibabaSpec := storeSpec.Provider.Alibaba
  125. credentials, err := newAuth(ctx, kube, store, namespace)
  126. if err != nil {
  127. return nil, fmt.Errorf("failed to create Alibaba credentials: %w", err)
  128. }
  129. config := &openapi.Config{
  130. RegionId: esutils.Ptr(alibabaSpec.RegionID),
  131. Credential: credentials,
  132. }
  133. options := newOptions(store)
  134. client, err := newClient(config, options)
  135. if err != nil {
  136. return nil, fmt.Errorf(errAlibabaClient, err)
  137. }
  138. kms.Client = client
  139. kms.Config = config
  140. return kms, nil
  141. }
  142. func newOptions(store esv1.GenericStore) *util.RuntimeOptions {
  143. storeSpec := store.GetSpec()
  144. options := &util.RuntimeOptions{}
  145. // Setup retry options, if present in storeSpec
  146. if storeSpec.RetrySettings != nil {
  147. var retryAmount int
  148. if storeSpec.RetrySettings.MaxRetries != nil {
  149. retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
  150. } else {
  151. retryAmount = 3
  152. }
  153. options.Autoretry = esutils.Ptr(true)
  154. options.MaxAttempts = esutils.Ptr(retryAmount)
  155. }
  156. return options
  157. }
  158. func newAuth(ctx context.Context, kube kclient.Client, store esv1.GenericStore, namespace string) (credential.Credential, error) {
  159. storeSpec := store.GetSpec()
  160. alibabaSpec := storeSpec.Provider.Alibaba
  161. switch {
  162. case alibabaSpec.Auth.RRSAAuth != nil:
  163. credentials, err := newRRSAAuth(store)
  164. if err != nil {
  165. return nil, fmt.Errorf("failed to create Alibaba OIDC credentials: %w", err)
  166. }
  167. return credentials, nil
  168. case alibabaSpec.Auth.SecretRef != nil:
  169. credentials, err := newAccessKeyAuth(ctx, kube, store, namespace)
  170. if err != nil {
  171. return nil, fmt.Errorf("failed to create Alibaba AccessKey credentials: %w", err)
  172. }
  173. return credentials, nil
  174. default:
  175. return nil, errors.New("alibaba authentication methods wasn't provided")
  176. }
  177. }
  178. func newRRSAAuth(store esv1.GenericStore) (credential.Credential, error) {
  179. storeSpec := store.GetSpec()
  180. alibabaSpec := storeSpec.Provider.Alibaba
  181. credentialConfig := &credential.Config{
  182. OIDCProviderArn: &alibabaSpec.Auth.RRSAAuth.OIDCProviderARN,
  183. OIDCTokenFilePath: &alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath,
  184. RoleArn: &alibabaSpec.Auth.RRSAAuth.RoleARN,
  185. RoleSessionName: &alibabaSpec.Auth.RRSAAuth.SessionName,
  186. Type: esutils.Ptr("oidc_role_arn"),
  187. ConnectTimeout: esutils.Ptr(30 * 1000),
  188. Timeout: esutils.Ptr(60 * 1000),
  189. }
  190. return credential.NewCredential(credentialConfig)
  191. }
  192. func newAccessKeyAuth(ctx context.Context, kube kclient.Client, store esv1.GenericStore, namespace string) (credential.Credential, error) {
  193. storeSpec := store.GetSpec()
  194. alibabaSpec := storeSpec.Provider.Alibaba
  195. storeKind := store.GetObjectKind().GroupVersionKind().Kind
  196. accessKeyID, err := resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, &alibabaSpec.Auth.SecretRef.AccessKeyID)
  197. if err != nil {
  198. return nil, fmt.Errorf(errFetchAccessKeyID, err)
  199. }
  200. accessKeySecret, err := resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, &alibabaSpec.Auth.SecretRef.AccessKeySecret)
  201. if err != nil {
  202. return nil, fmt.Errorf(errFetchAccessKeySecret, err)
  203. }
  204. credentialConfig := &credential.Config{
  205. AccessKeyId: esutils.Ptr(accessKeyID),
  206. AccessKeySecret: esutils.Ptr(accessKeySecret),
  207. Type: esutils.Ptr("access_key"),
  208. ConnectTimeout: esutils.Ptr(30),
  209. Timeout: esutils.Ptr(60),
  210. }
  211. return credential.NewCredential(credentialConfig)
  212. }
  213. // Close cleans up resources when the provider is done being used.
  214. func (kms *KeyManagementService) Close(_ context.Context) error {
  215. return nil
  216. }
  217. // Validate checks if the provider is properly configured and ready to use.
  218. func (kms *KeyManagementService) Validate() (esv1.ValidationResult, error) {
  219. err := retry.Do(
  220. func() error {
  221. if _, err := kms.Config.Credential.GetCredential(); err != nil {
  222. return err
  223. }
  224. return nil
  225. },
  226. retry.Attempts(5),
  227. )
  228. if err != nil {
  229. return esv1.ValidationResultError, SanitizeErr(err)
  230. }
  231. return esv1.ValidationResultReady, nil
  232. }
  233. // ValidateStore validates the configuration of the store.
  234. func (kms *KeyManagementService) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  235. storeSpec := store.GetSpec()
  236. alibabaSpec := storeSpec.Provider.Alibaba
  237. regionID := alibabaSpec.RegionID
  238. if regionID == "" {
  239. return nil, errors.New("missing alibaba region")
  240. }
  241. return nil, kms.validateStoreAuth(store)
  242. }
  243. func (kms *KeyManagementService) validateStoreAuth(store esv1.GenericStore) error {
  244. storeSpec := store.GetSpec()
  245. alibabaSpec := storeSpec.Provider.Alibaba
  246. switch {
  247. case alibabaSpec.Auth.RRSAAuth != nil:
  248. return kms.validateStoreRRSAAuth(store)
  249. case alibabaSpec.Auth.SecretRef != nil:
  250. return kms.validateStoreAccessKeyAuth(store)
  251. default:
  252. return errors.New("missing alibaba auth provider")
  253. }
  254. }
  255. func (kms *KeyManagementService) validateStoreRRSAAuth(store esv1.GenericStore) error {
  256. storeSpec := store.GetSpec()
  257. alibabaSpec := storeSpec.Provider.Alibaba
  258. if alibabaSpec.Auth.RRSAAuth.OIDCProviderARN == "" {
  259. return errors.New("missing alibaba OIDC proivder ARN")
  260. }
  261. if alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath == "" {
  262. return errors.New("missing alibaba OIDC token file path")
  263. }
  264. if alibabaSpec.Auth.RRSAAuth.RoleARN == "" {
  265. return errors.New("missing alibaba Assume Role ARN")
  266. }
  267. if alibabaSpec.Auth.RRSAAuth.SessionName == "" {
  268. return errors.New("missing alibaba session name")
  269. }
  270. return nil
  271. }
  272. func (kms *KeyManagementService) validateStoreAccessKeyAuth(store esv1.GenericStore) error {
  273. storeSpec := store.GetSpec()
  274. alibabaSpec := storeSpec.Provider.Alibaba
  275. accessKeyID := alibabaSpec.Auth.SecretRef.AccessKeyID
  276. err := esutils.ValidateSecretSelector(store, accessKeyID)
  277. if err != nil {
  278. return err
  279. }
  280. if accessKeyID.Name == "" {
  281. return errors.New("missing alibaba access ID name")
  282. }
  283. if accessKeyID.Key == "" {
  284. return errors.New("missing alibaba access ID key")
  285. }
  286. accessKeySecret := alibabaSpec.Auth.SecretRef.AccessKeySecret
  287. err = esutils.ValidateSecretSelector(store, accessKeySecret)
  288. if err != nil {
  289. return err
  290. }
  291. if accessKeySecret.Name == "" {
  292. return errors.New("missing alibaba access key secret name")
  293. }
  294. if accessKeySecret.Key == "" {
  295. return errors.New("missing alibaba access key secret key")
  296. }
  297. return nil
  298. }
  299. // NewProvider creates a new Provider instance.
  300. func NewProvider() esv1.Provider {
  301. return &KeyManagementService{}
  302. }
  303. // ProviderSpec returns the provider specification for registration.
  304. func ProviderSpec() *esv1.SecretStoreProvider {
  305. return &esv1.SecretStoreProvider{
  306. Alibaba: &esv1.AlibabaProvider{},
  307. }
  308. }
  309. // MaintenanceStatus returns the maintenance status of the provider.
  310. func MaintenanceStatus() esv1.MaintenanceStatus {
  311. return esv1.MaintenanceStatusDeprecated
  312. }