kms.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  25. "k8s.io/apimachinery/pkg/types"
  26. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. "github.com/external-secrets/external-secrets/pkg/utils"
  29. )
  30. const (
  31. errAlibabaClient = "cannot setup new Alibaba client: %w"
  32. errAlibabaCredSecretName = "invalid Alibaba SecretStore resource: missing Alibaba APIKey"
  33. errUninitalizedAlibabaProvider = "provider Alibaba is not initialized"
  34. errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterStore, missing AccessKeyID namespace"
  35. errInvalidClusterStoreMissingSKNamespace = "invalid ClusterStore, missing namespace"
  36. errFetchAKIDSecret = "could not fetch AccessKeyID secret: %w"
  37. errMissingSAK = "missing AccessSecretKey"
  38. errMissingAKID = "missing AccessKeyID"
  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. }
  47. type SMInterface interface {
  48. GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error)
  49. Endpoint() string
  50. }
  51. func (kms *KeyManagementService) PushSecret(_ context.Context, _ []byte, _ *apiextensionsv1.JSON, _ esv1beta1.PushRemoteRef) error {
  52. return fmt.Errorf("not implemented")
  53. }
  54. func (kms *KeyManagementService) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
  55. return fmt.Errorf("not implemented")
  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("GetAllSecrets not implemented")
  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. credentialsSecret := &corev1.Secret{}
  190. credentialsSecretName := alibabaSpec.Auth.SecretRef.AccessKeyID.Name
  191. if credentialsSecretName == "" {
  192. return nil, fmt.Errorf(errAlibabaCredSecretName)
  193. }
  194. objectKey := types.NamespacedName{
  195. Name: credentialsSecretName,
  196. Namespace: namespace,
  197. }
  198. // only ClusterStore is allowed to set namespace (and then it's required)
  199. if storeKind == esv1beta1.ClusterSecretStoreKind {
  200. if alibabaSpec.Auth.SecretRef.AccessKeyID.Namespace == nil {
  201. return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
  202. }
  203. objectKey.Namespace = *alibabaSpec.Auth.SecretRef.AccessKeyID.Namespace
  204. }
  205. err := kube.Get(ctx, objectKey, credentialsSecret)
  206. if err != nil {
  207. return nil, fmt.Errorf(errFetchAKIDSecret, err)
  208. }
  209. objectKey = types.NamespacedName{
  210. Name: alibabaSpec.Auth.SecretRef.AccessKeySecret.Name,
  211. Namespace: namespace,
  212. }
  213. if storeKind == esv1beta1.ClusterSecretStoreKind {
  214. if alibabaSpec.Auth.SecretRef.AccessKeySecret.Namespace == nil {
  215. return nil, fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
  216. }
  217. objectKey.Namespace = *alibabaSpec.Auth.SecretRef.AccessKeySecret.Namespace
  218. }
  219. accessKeyID := credentialsSecret.Data[alibabaSpec.Auth.SecretRef.AccessKeyID.Key]
  220. if (accessKeyID == nil) || (len(accessKeyID) == 0) {
  221. return nil, fmt.Errorf(errMissingAKID)
  222. }
  223. accessKeySecret := credentialsSecret.Data[alibabaSpec.Auth.SecretRef.AccessKeySecret.Key]
  224. if (accessKeySecret == nil) || (len(accessKeySecret) == 0) {
  225. return nil, fmt.Errorf(errMissingSAK)
  226. }
  227. credentialConfig := &credential.Config{
  228. AccessKeyId: utils.Ptr(string(accessKeyID)),
  229. AccessKeySecret: utils.Ptr(string(accessKeySecret)),
  230. Type: utils.Ptr("access_key"),
  231. ConnectTimeout: utils.Ptr(30),
  232. Timeout: utils.Ptr(60),
  233. }
  234. return credential.NewCredential(credentialConfig)
  235. }
  236. func (kms *KeyManagementService) Close(_ context.Context) error {
  237. return nil
  238. }
  239. func (kms *KeyManagementService) Validate() (esv1beta1.ValidationResult, error) {
  240. err := retry.Do(
  241. func() error {
  242. _, err := kms.Config.Credential.GetSecurityToken()
  243. if err != nil {
  244. return err
  245. }
  246. return nil
  247. },
  248. retry.Attempts(5),
  249. )
  250. if err != nil {
  251. return esv1beta1.ValidationResultError, SanitizeErr(err)
  252. }
  253. return esv1beta1.ValidationResultReady, nil
  254. }
  255. func (kms *KeyManagementService) ValidateStore(store esv1beta1.GenericStore) error {
  256. storeSpec := store.GetSpec()
  257. alibabaSpec := storeSpec.Provider.Alibaba
  258. regionID := alibabaSpec.RegionID
  259. if regionID == "" {
  260. return fmt.Errorf("missing alibaba region")
  261. }
  262. return kms.validateStoreAuth(store)
  263. }
  264. func (kms *KeyManagementService) validateStoreAuth(store esv1beta1.GenericStore) error {
  265. storeSpec := store.GetSpec()
  266. alibabaSpec := storeSpec.Provider.Alibaba
  267. switch {
  268. case alibabaSpec.Auth.RRSAAuth != nil:
  269. return kms.validateStoreRRSAAuth(store)
  270. case alibabaSpec.Auth.SecretRef != nil:
  271. return kms.validateStoreAccessKeyAuth(store)
  272. default:
  273. return fmt.Errorf("missing alibaba auth provider")
  274. }
  275. }
  276. func (kms *KeyManagementService) validateStoreRRSAAuth(store esv1beta1.GenericStore) error {
  277. storeSpec := store.GetSpec()
  278. alibabaSpec := storeSpec.Provider.Alibaba
  279. if alibabaSpec.Auth.RRSAAuth.OIDCProviderARN == "" {
  280. return fmt.Errorf("missing alibaba OIDC proivder ARN")
  281. }
  282. if alibabaSpec.Auth.RRSAAuth.OIDCTokenFilePath == "" {
  283. return fmt.Errorf("missing alibaba OIDC token file path")
  284. }
  285. if alibabaSpec.Auth.RRSAAuth.RoleARN == "" {
  286. return fmt.Errorf("missing alibaba Assume Role ARN")
  287. }
  288. if alibabaSpec.Auth.RRSAAuth.SessionName == "" {
  289. return fmt.Errorf("missing alibaba session name")
  290. }
  291. return nil
  292. }
  293. func (kms *KeyManagementService) validateStoreAccessKeyAuth(store esv1beta1.GenericStore) error {
  294. storeSpec := store.GetSpec()
  295. alibabaSpec := storeSpec.Provider.Alibaba
  296. accessKeyID := alibabaSpec.Auth.SecretRef.AccessKeyID
  297. err := utils.ValidateSecretSelector(store, accessKeyID)
  298. if err != nil {
  299. return err
  300. }
  301. if accessKeyID.Name == "" {
  302. return fmt.Errorf("missing alibaba access ID name")
  303. }
  304. if accessKeyID.Key == "" {
  305. return fmt.Errorf("missing alibaba access ID key")
  306. }
  307. accessKeySecret := alibabaSpec.Auth.SecretRef.AccessKeySecret
  308. err = utils.ValidateSecretSelector(store, accessKeySecret)
  309. if err != nil {
  310. return err
  311. }
  312. if accessKeySecret.Name == "" {
  313. return fmt.Errorf("missing alibaba access key secret name")
  314. }
  315. if accessKeySecret.Key == "" {
  316. return fmt.Errorf("missing alibaba access key secret key")
  317. }
  318. return nil
  319. }
  320. func init() {
  321. esv1beta1.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
  322. Alibaba: &esv1beta1.AlibabaProvider{},
  323. })
  324. }