kms.go 11 KB

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