kms.go 10 KB

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