kms.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 alibaba
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
  19. kmssdk "github.com/alibabacloud-go/kms-20160120/v3/client"
  20. util "github.com/alibabacloud-go/tea-utils/v2/service"
  21. credential "github.com/aliyun/credentials-go/credentials"
  22. "github.com/avast/retry-go/v4"
  23. "github.com/tidwall/gjson"
  24. corev1 "k8s.io/api/core/v1"
  25. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  26. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  29. prov "github.com/external-secrets/external-secrets/apis/providers/v1alpha1"
  30. "github.com/external-secrets/external-secrets/pkg/utils"
  31. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  32. )
  33. const (
  34. errAlibabaClient = "cannot setup new Alibaba client: %w"
  35. errUninitalizedAlibabaProvider = "provider Alibaba is not initialized"
  36. errFetchAccessKeyID = "could not fetch AccessKeyID secret: %w"
  37. errFetchAccessKeySecret = "could not fetch AccessKeySecret secret: %w"
  38. errNotImplemented = "not implemented"
  39. )
  40. // https://github.com/external-secrets/external-secrets/issues/644
  41. var _ esv1beta1.SecretsClient = &KeyManagementService{}
  42. var _ esv1beta1.Provider = &KeyManagementService{}
  43. type KeyManagementService struct {
  44. Client SMInterface
  45. Config *openapi.Config
  46. storeKind string
  47. }
  48. type SMInterface interface {
  49. GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error)
  50. Endpoint() string
  51. }
  52. func (kms *KeyManagementService) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
  53. return errors.New(errNotImplemented)
  54. }
  55. func (kms *KeyManagementService) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  56. return errors.New(errNotImplemented)
  57. }
  58. func (kms *KeyManagementService) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
  59. return false, errors.New(errNotImplemented)
  60. }
  61. // Empty GetAllSecrets.
  62. func (kms *KeyManagementService) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  63. // TO be implemented
  64. return nil, errors.New(errNotImplemented)
  65. }
  66. // GetSecret returns a single secret from the provider.
  67. func (kms *KeyManagementService) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  68. if utils.IsNil(kms.Client) {
  69. return nil, errors.New(errUninitalizedAlibabaProvider)
  70. }
  71. request := &kmssdk.GetSecretValueRequest{
  72. SecretName: &ref.Key,
  73. }
  74. if ref.Version != "" {
  75. request.VersionId = &ref.Version
  76. }
  77. secretOut, err := kms.Client.GetSecretValue(ctx, request)
  78. if err != nil {
  79. return nil, SanitizeErr(err)
  80. }
  81. if ref.Property == "" {
  82. if utils.Deref(secretOut.SecretData) != "" {
  83. return []byte(utils.Deref(secretOut.SecretData)), nil
  84. }
  85. return nil, fmt.Errorf("invalid secret received. no secret string nor binary for key: %s", ref.Key)
  86. }
  87. var payload string
  88. if utils.Deref(secretOut.SecretData) != "" {
  89. payload = utils.Deref(secretOut.SecretData)
  90. }
  91. val := gjson.Get(payload, ref.Property)
  92. if !val.Exists() {
  93. return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
  94. }
  95. return []byte(val.String()), nil
  96. }
  97. // GetSecretMap returns multiple k/v pairs from the provider.
  98. func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  99. data, err := kms.GetSecret(ctx, ref)
  100. if err != nil {
  101. return nil, err
  102. }
  103. kv := make(map[string]string)
  104. err = json.Unmarshal(data, &kv)
  105. if err != nil {
  106. return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
  107. }
  108. secretData := make(map[string][]byte)
  109. for k, v := range kv {
  110. secretData[k] = []byte(v)
  111. }
  112. return secretData, nil
  113. }
  114. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  115. func (kms *KeyManagementService) Capabilities() esv1beta1.SecretStoreCapabilities {
  116. return esv1beta1.SecretStoreReadOnly
  117. }
  118. func (kms *KeyManagementService) Convert(in esv1beta1.GenericStore) (kclient.Object, error) {
  119. out := &prov.Alibaba{}
  120. tmp := map[string]interface{}{
  121. "spec": in.GetSpec().Provider.Alibaba,
  122. }
  123. d, err := json.Marshal(tmp)
  124. if err != nil {
  125. return nil, err
  126. }
  127. err = json.Unmarshal(d, out)
  128. if err != nil {
  129. return nil, fmt.Errorf("could not convert %v in a valid fake provider: %w", in.GetName(), err)
  130. }
  131. return out, nil
  132. }
  133. func (kms *KeyManagementService) NewClientFromObj(ctx context.Context, obj kclient.Object, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  134. p, ok := obj.(*prov.Alibaba)
  135. if !ok {
  136. return nil, errors.New("could not validate provider")
  137. }
  138. alibabaSpec := &p.Spec
  139. credentials, err := newAuth(ctx, kube, alibabaSpec, namespace, kms.storeKind)
  140. if err != nil {
  141. return nil, fmt.Errorf("failed to create Alibaba credentials: %w", err)
  142. }
  143. config := &openapi.Config{
  144. RegionId: utils.Ptr(alibabaSpec.RegionID),
  145. Credential: credentials,
  146. }
  147. options := newOptions(alibabaSpec)
  148. client, err := newClient(config, options)
  149. if err != nil {
  150. return nil, fmt.Errorf(errAlibabaClient, err)
  151. }
  152. kms.Client = client
  153. kms.Config = config
  154. return kms, nil
  155. }
  156. func (kms *KeyManagementService) ApplyReferent(spec kclient.Object, caller esmeta.ReferentCallOrigin, _ string) (kclient.Object, error) {
  157. converted, ok := spec.(*prov.Akeyless)
  158. out := converted.DeepCopy()
  159. if !ok {
  160. return nil, fmt.Errorf("could not convert source object %v into 'fake' provider type: object from type %T", spec.GetName(), spec)
  161. }
  162. switch caller {
  163. case esmeta.ReferentCallClusterSecretStore:
  164. kms.storeKind = esv1beta1.ClusterSecretStoreKind
  165. case esmeta.ReferentCallSecretStore:
  166. case esmeta.ReferentCallProvider:
  167. default:
  168. }
  169. return out, nil
  170. }
  171. // NewClient constructs a new secrets client based on the provided store.
  172. func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  173. return nil, errors.New("no longer supported")
  174. }
  175. func newOptions(storeSpec *prov.AlibabaSpec) *util.RuntimeOptions {
  176. options := &util.RuntimeOptions{}
  177. // Setup retry options, if present in storeSpec
  178. if storeSpec.RetrySettings != nil {
  179. var retryAmount int
  180. if storeSpec.RetrySettings.MaxRetries != nil {
  181. retryAmount = int(*storeSpec.RetrySettings.MaxRetries)
  182. } else {
  183. retryAmount = 3
  184. }
  185. options.Autoretry = utils.Ptr(true)
  186. options.MaxAttempts = utils.Ptr(retryAmount)
  187. }
  188. return options
  189. }
  190. func newAuth(ctx context.Context, kube kclient.Client, alibabaSpec *prov.AlibabaSpec, namespace, storeKind string) (credential.Credential, error) {
  191. switch {
  192. case alibabaSpec.Auth.RRSAAuth != nil:
  193. credentials, err := newRRSAAuth(alibabaSpec)
  194. if err != nil {
  195. return nil, fmt.Errorf("failed to create Alibaba OIDC credentials: %w", err)
  196. }
  197. return credentials, nil
  198. case alibabaSpec.Auth.SecretRef != nil:
  199. credentials, err := newAccessKeyAuth(ctx, kube, alibabaSpec, namespace, storeKind)
  200. if err != nil {
  201. return nil, fmt.Errorf("failed to create Alibaba AccessKey credentials: %w", err)
  202. }
  203. return credentials, nil
  204. default:
  205. return nil, errors.New("alibaba authentication methods wasn't provided")
  206. }
  207. }
  208. func newRRSAAuth(alibabaSpec *prov.AlibabaSpec) (credential.Credential, error) {
  209. credentialConfig := &credential.Config{
  210. OIDCProviderArn: &alibabaSpec.Auth.RRSAAuth.OIDCProviderARN,
  211. OIDCTokenFilePath: &alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath,
  212. RoleArn: &alibabaSpec.Auth.RRSAAuth.RoleARN,
  213. RoleSessionName: &alibabaSpec.Auth.RRSAAuth.SessionName,
  214. Type: utils.Ptr("oidc_role_arn"),
  215. ConnectTimeout: utils.Ptr(30),
  216. Timeout: utils.Ptr(60),
  217. }
  218. return credential.NewCredential(credentialConfig)
  219. }
  220. func newAccessKeyAuth(ctx context.Context, kube kclient.Client, alibabaSpec *prov.AlibabaSpec, namespace, storeKind string) (credential.Credential, error) {
  221. accessKeyID, err := resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, &alibabaSpec.Auth.SecretRef.AccessKeyID)
  222. if err != nil {
  223. return nil, fmt.Errorf(errFetchAccessKeyID, err)
  224. }
  225. accessKeySecret, err := resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, &alibabaSpec.Auth.SecretRef.AccessKeySecret)
  226. if err != nil {
  227. return nil, fmt.Errorf(errFetchAccessKeySecret, err)
  228. }
  229. credentialConfig := &credential.Config{
  230. AccessKeyId: utils.Ptr(accessKeyID),
  231. AccessKeySecret: utils.Ptr(accessKeySecret),
  232. Type: utils.Ptr("access_key"),
  233. ConnectTimeout: utils.Ptr(30),
  234. Timeout: utils.Ptr(60),
  235. }
  236. return credential.NewCredential(credentialConfig)
  237. }
  238. func (kms *KeyManagementService) Close(_ context.Context) error {
  239. return nil
  240. }
  241. func (kms *KeyManagementService) Validate() (esv1beta1.ValidationResult, error) {
  242. err := retry.Do(
  243. func() error {
  244. _, err := kms.Config.Credential.GetCredential()
  245. if err != nil {
  246. return err
  247. }
  248. return nil
  249. },
  250. retry.Attempts(5),
  251. )
  252. if err != nil {
  253. return esv1beta1.ValidationResultError, SanitizeErr(err)
  254. }
  255. return esv1beta1.ValidationResultReady, nil
  256. }
  257. func (kms *KeyManagementService) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
  258. storeSpec := store.GetSpec()
  259. alibabaSpec := storeSpec.Provider.Alibaba
  260. regionID := alibabaSpec.RegionID
  261. if regionID == "" {
  262. return nil, errors.New("missing alibaba region")
  263. }
  264. return nil, kms.validateStoreAuth(store)
  265. }
  266. func (kms *KeyManagementService) validateStoreAuth(store esv1beta1.GenericStore) error {
  267. storeSpec := store.GetSpec()
  268. alibabaSpec := storeSpec.Provider.Alibaba
  269. switch {
  270. case alibabaSpec.Auth.RRSAAuth != nil:
  271. return kms.validateStoreRRSAAuth(store)
  272. case alibabaSpec.Auth.SecretRef != nil:
  273. return kms.validateStoreAccessKeyAuth(store)
  274. default:
  275. return errors.New("missing alibaba auth provider")
  276. }
  277. }
  278. func (kms *KeyManagementService) validateStoreRRSAAuth(store esv1beta1.GenericStore) error {
  279. storeSpec := store.GetSpec()
  280. alibabaSpec := storeSpec.Provider.Alibaba
  281. if alibabaSpec.Auth.RRSAAuth.OIDCProviderARN == "" {
  282. return errors.New("missing alibaba OIDC proivder ARN")
  283. }
  284. if alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath == "" {
  285. return errors.New("missing alibaba OIDC token file path")
  286. }
  287. if alibabaSpec.Auth.RRSAAuth.RoleARN == "" {
  288. return errors.New("missing alibaba Assume Role ARN")
  289. }
  290. if alibabaSpec.Auth.RRSAAuth.SessionName == "" {
  291. return errors.New("missing alibaba session name")
  292. }
  293. return nil
  294. }
  295. func (kms *KeyManagementService) validateStoreAccessKeyAuth(store esv1beta1.GenericStore) error {
  296. storeSpec := store.GetSpec()
  297. alibabaSpec := storeSpec.Provider.Alibaba
  298. accessKeyID := alibabaSpec.Auth.SecretRef.AccessKeyID
  299. err := utils.ValidateSecretSelector(store, accessKeyID)
  300. if err != nil {
  301. return err
  302. }
  303. if accessKeyID.Name == "" {
  304. return errors.New("missing alibaba access ID name")
  305. }
  306. if accessKeyID.Key == "" {
  307. return errors.New("missing alibaba access ID key")
  308. }
  309. accessKeySecret := alibabaSpec.Auth.SecretRef.AccessKeySecret
  310. err = utils.ValidateSecretSelector(store, accessKeySecret)
  311. if err != nil {
  312. return err
  313. }
  314. if accessKeySecret.Name == "" {
  315. return errors.New("missing alibaba access key secret name")
  316. }
  317. if accessKeySecret.Key == "" {
  318. return errors.New("missing alibaba access key secret key")
  319. }
  320. return nil
  321. }
  322. func init() {
  323. esv1beta1.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
  324. Alibaba: &esv1beta1.AlibabaProvider{},
  325. })
  326. esv1beta1.RegisterByName(&KeyManagementService{}, prov.AlibabaKind)
  327. ref := esmeta.ProviderRef{
  328. APIVersion: prov.Group + "/" + prov.Version,
  329. Kind: prov.AlibabaKind,
  330. }
  331. prov.RefRegister(&prov.Alibaba{}, ref)
  332. }