kms.go 11 KB

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