kms.go 11 KB

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