akeyless.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package akeyless
  14. import (
  15. "bytes"
  16. "context"
  17. "crypto/tls"
  18. "crypto/x509"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "net/http"
  23. "net/url"
  24. "slices"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "github.com/akeylesslabs/akeyless-go/v4"
  29. "github.com/tidwall/gjson"
  30. corev1 "k8s.io/api/core/v1"
  31. "k8s.io/client-go/kubernetes"
  32. typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  33. "sigs.k8s.io/controller-runtime/pkg/client"
  34. ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
  35. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  36. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  37. "github.com/external-secrets/external-secrets/pkg/find"
  38. "github.com/external-secrets/external-secrets/pkg/utils"
  39. )
  40. type AkeylessCtx string
  41. const (
  42. defaultAPIUrl = "https://api.akeyless.io"
  43. extSecretManagedTag = "k8s-external-secrets"
  44. aKeylessToken AkeylessCtx = "AKEYLESS_TOKEN"
  45. )
  46. // https://github.com/external-secrets/external-secrets/issues/644
  47. var _ esv1.SecretsClient = &Akeyless{}
  48. var _ esv1.Provider = &Provider{}
  49. // Provider satisfies the provider interface.
  50. type Provider struct{}
  51. // akeylessBase satisfies the provider.SecretsClient interface.
  52. type akeylessBase struct {
  53. kube client.Client
  54. store esv1.GenericStore
  55. storeKind string
  56. corev1 typedcorev1.CoreV1Interface
  57. namespace string
  58. akeylessGwAPIURL string
  59. RestAPI *akeyless.V2ApiService
  60. }
  61. type Akeyless struct {
  62. Client akeylessVaultInterface
  63. url string
  64. }
  65. type Item struct {
  66. ItemName string `json:"item_name"`
  67. ItemType string `json:"item_type"`
  68. LastVersion int32 `json:"last_version"`
  69. }
  70. type akeylessVaultInterface interface {
  71. GetSecretByType(ctx context.Context, secretName string, version int32) (string, error)
  72. TokenFromSecretRef(ctx context.Context) (string, error)
  73. ListSecrets(ctx context.Context, path, tag string) ([]string, error)
  74. DescribeItem(ctx context.Context, itemName string) (*akeyless.Item, error)
  75. CreateSecret(ctx context.Context, remoteKey, data string) error
  76. UpdateSecret(ctx context.Context, remoteKey, data string) error
  77. DeleteSecret(ctx context.Context, remoteKey string) error
  78. }
  79. func init() {
  80. esv1.Register(&Provider{}, &esv1.SecretStoreProvider{
  81. Akeyless: &esv1.AkeylessProvider{},
  82. }, esv1.MaintenanceStatusMaintained)
  83. }
  84. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  85. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  86. return esv1.SecretStoreReadOnly
  87. }
  88. // NewClient constructs a new secrets client based on the provided store.
  89. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
  90. // controller-runtime/client does not support TokenRequest or other subresource APIs
  91. // so we need to construct our own client and use it to fetch tokens
  92. // (for Kubernetes service account token auth)
  93. restCfg, err := ctrlcfg.GetConfig()
  94. if err != nil {
  95. return nil, err
  96. }
  97. clientset, err := kubernetes.NewForConfig(restCfg)
  98. if err != nil {
  99. return nil, err
  100. }
  101. return newClient(ctx, store, kube, clientset.CoreV1(), namespace)
  102. }
  103. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  104. storeSpec := store.GetSpec()
  105. akeylessSpec := storeSpec.Provider.Akeyless
  106. akeylessGWApiURL := akeylessSpec.AkeylessGWApiURL
  107. if akeylessGWApiURL != nil && *akeylessGWApiURL != "" {
  108. url, err := url.Parse(*akeylessGWApiURL)
  109. if err != nil {
  110. return nil, errors.New(errInvalidAkeylessURL)
  111. }
  112. if url.Host == "" {
  113. return nil, errors.New(errInvalidAkeylessURL)
  114. }
  115. }
  116. if akeylessSpec.Auth.KubernetesAuth != nil {
  117. if akeylessSpec.Auth.KubernetesAuth.ServiceAccountRef != nil {
  118. if err := utils.ValidateReferentServiceAccountSelector(store, *akeylessSpec.Auth.KubernetesAuth.ServiceAccountRef); err != nil {
  119. return nil, fmt.Errorf(errInvalidKubeSA, err)
  120. }
  121. }
  122. if akeylessSpec.Auth.KubernetesAuth.SecretRef != nil {
  123. err := utils.ValidateSecretSelector(store, *akeylessSpec.Auth.KubernetesAuth.SecretRef)
  124. if err != nil {
  125. return nil, err
  126. }
  127. }
  128. if akeylessSpec.Auth.KubernetesAuth.AccessID == "" {
  129. return nil, errors.New("missing kubernetes auth-method access-id")
  130. }
  131. if akeylessSpec.Auth.KubernetesAuth.K8sConfName == "" {
  132. return nil, errors.New("missing kubernetes config name")
  133. }
  134. return nil, nil
  135. }
  136. accessID := akeylessSpec.Auth.SecretRef.AccessID
  137. err := utils.ValidateSecretSelector(store, accessID)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if accessID.Name == "" {
  142. return nil, errors.New(errInvalidAkeylessAccessIDName)
  143. }
  144. if accessID.Key == "" {
  145. return nil, errors.New(errInvalidAkeylessAccessIDKey)
  146. }
  147. accessType := akeylessSpec.Auth.SecretRef.AccessType
  148. err = utils.ValidateSecretSelector(store, accessType)
  149. if err != nil {
  150. return nil, err
  151. }
  152. accessTypeParam := akeylessSpec.Auth.SecretRef.AccessTypeParam
  153. err = utils.ValidateSecretSelector(store, accessTypeParam)
  154. if err != nil {
  155. return nil, err
  156. }
  157. return nil, nil
  158. }
  159. func newClient(ctx context.Context, store esv1.GenericStore, kube client.Client, corev1 typedcorev1.CoreV1Interface, namespace string) (esv1.SecretsClient, error) {
  160. akl := &akeylessBase{
  161. kube: kube,
  162. store: store,
  163. namespace: namespace,
  164. corev1: corev1,
  165. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  166. }
  167. spec, err := GetAKeylessProvider(store)
  168. if err != nil {
  169. return nil, err
  170. }
  171. akeylessGwAPIURL := defaultAPIUrl
  172. if spec != nil && spec.AkeylessGWApiURL != nil && *spec.AkeylessGWApiURL != "" {
  173. akeylessGwAPIURL = getV2Url(*spec.AkeylessGWApiURL)
  174. }
  175. if spec.Auth == nil {
  176. return nil, errors.New("missing Auth in store config")
  177. }
  178. client, err := akl.getAkeylessHTTPClient(ctx, spec)
  179. if err != nil {
  180. return nil, err
  181. }
  182. RestAPIClient := akeyless.NewAPIClient(&akeyless.Configuration{
  183. HTTPClient: client,
  184. Servers: []akeyless.ServerConfiguration{
  185. {
  186. URL: akeylessGwAPIURL,
  187. },
  188. },
  189. }).V2Api
  190. akl.akeylessGwAPIURL = akeylessGwAPIURL
  191. akl.RestAPI = RestAPIClient
  192. return &Akeyless{Client: akl, url: akeylessGwAPIURL}, nil
  193. }
  194. func (a *Akeyless) contextWithToken(ctx context.Context) (context.Context, error) {
  195. if v := ctx.Value(aKeylessToken); v != nil {
  196. return ctx, nil
  197. }
  198. token, err := a.Client.TokenFromSecretRef(ctx)
  199. if err != nil {
  200. return nil, err
  201. }
  202. return context.WithValue(ctx, aKeylessToken, token), nil
  203. }
  204. func (a *Akeyless) Close(_ context.Context) error {
  205. return nil
  206. }
  207. func (a *Akeyless) Validate() (esv1.ValidationResult, error) {
  208. timeout := 15 * time.Second
  209. url := a.url
  210. if err := utils.NetworkValidate(url, timeout); err != nil {
  211. return esv1.ValidationResultError, err
  212. }
  213. return esv1.ValidationResultReady, nil
  214. }
  215. // Implements store.Client.GetSecret Interface.
  216. // Retrieves a secret with the secret name defined in ref.Name.
  217. func (a *Akeyless) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  218. if utils.IsNil(a.Client) {
  219. return nil, errors.New(errUninitalizedAkeylessProvider)
  220. }
  221. ctx, err := a.contextWithToken(ctx)
  222. if err != nil {
  223. return nil, err
  224. }
  225. version := int32(0)
  226. if ref.Version != "" {
  227. i, err := strconv.ParseInt(ref.Version, 10, 32)
  228. if err == nil {
  229. version = int32(i)
  230. }
  231. }
  232. value, err := a.Client.GetSecretByType(ctx, ref.Key, version)
  233. if err != nil {
  234. return nil, err
  235. }
  236. if ref.Property == "" {
  237. if value != "" {
  238. return []byte(value), nil
  239. }
  240. return nil, fmt.Errorf("invalid value received, found no value string : %s", ref.Key)
  241. }
  242. // We need to search if a given key with a . exists before using gjson operations.
  243. idx := strings.Index(ref.Property, ".")
  244. if idx > -1 {
  245. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  246. val := gjson.Get(value, refProperty)
  247. if val.Exists() {
  248. return []byte(val.String()), nil
  249. }
  250. }
  251. val := gjson.Get(value, ref.Property)
  252. if !val.Exists() {
  253. return nil, fmt.Errorf("key %s does not exist in value %s", ref.Property, ref.Key)
  254. }
  255. return []byte(val.String()), nil
  256. }
  257. // GetAllSecrets Implements store.Client.GetAllSecrets Interface.
  258. // Retrieves all secrets with defined in ref.Name or tags.
  259. func (a *Akeyless) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  260. if utils.IsNil(a.Client) {
  261. return nil, errors.New(errUninitalizedAkeylessProvider)
  262. }
  263. ctx, err := a.contextWithToken(ctx)
  264. if err != nil {
  265. return nil, err
  266. }
  267. searchPath := ""
  268. if ref.Path != nil {
  269. searchPath = *ref.Path
  270. if !strings.HasPrefix(searchPath, "/") {
  271. searchPath = "/" + searchPath
  272. }
  273. if !strings.HasSuffix(searchPath, "/") {
  274. searchPath += "/"
  275. }
  276. }
  277. if ref.Name != nil {
  278. return a.findSecretsFromName(ctx, searchPath, *ref.Name)
  279. }
  280. if len(ref.Tags) > 0 {
  281. return a.getSecrets(ctx, searchPath, ref.Tags)
  282. }
  283. return nil, errors.New("unexpected find operator")
  284. }
  285. func (a *Akeyless) getSecrets(ctx context.Context, searchPath string, tags map[string]string) (map[string][]byte, error) {
  286. var potentialSecretsName []string
  287. for _, v := range tags {
  288. potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, v)
  289. if err != nil {
  290. return nil, err
  291. }
  292. if len(potentialSecrets) > 0 {
  293. potentialSecretsName = append(potentialSecretsName, potentialSecrets...)
  294. }
  295. }
  296. if len(potentialSecretsName) == 0 {
  297. return nil, nil
  298. }
  299. secrets := make(map[string][]byte)
  300. for _, name := range potentialSecretsName {
  301. secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
  302. if err != nil {
  303. return nil, err
  304. }
  305. if secretValue != "" {
  306. secrets[name] = []byte(secretValue)
  307. }
  308. }
  309. return secrets, nil
  310. }
  311. func (a *Akeyless) findSecretsFromName(ctx context.Context, searchPath string, ref esv1.FindName) (map[string][]byte, error) {
  312. potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, "")
  313. if err != nil {
  314. return nil, err
  315. }
  316. if len(potentialSecrets) == 0 {
  317. return nil, nil
  318. }
  319. secrets := make(map[string][]byte)
  320. matcher, err := find.New(ref)
  321. if err != nil {
  322. return nil, err
  323. }
  324. for _, name := range potentialSecrets {
  325. ok := matcher.MatchName(name)
  326. if ok {
  327. secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
  328. if err != nil {
  329. return nil, err
  330. }
  331. if secretValue != "" {
  332. secrets[name] = []byte(secretValue)
  333. }
  334. }
  335. }
  336. return secrets, nil
  337. }
  338. // GetSecretMap implements store.Client.GetSecretMap Interface.
  339. // New version of GetSecretMap.
  340. func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  341. if utils.IsNil(a.Client) {
  342. return nil, errors.New(errUninitalizedAkeylessProvider)
  343. }
  344. val, err := a.GetSecret(ctx, ref)
  345. if err != nil {
  346. return nil, err
  347. }
  348. // Maps the json data to a string:string map
  349. kv := make(map[string]string)
  350. err = json.Unmarshal(val, &kv)
  351. if err != nil {
  352. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  353. }
  354. // Converts values in K:V pairs into bytes, while leaving keys as strings
  355. secretData := make(map[string][]byte)
  356. for k, v := range kv {
  357. secretData[k] = []byte(v)
  358. }
  359. return secretData, nil
  360. }
  361. func (a *Akeyless) SecretExists(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
  362. if utils.IsNil(a.Client) {
  363. return false, errors.New(errUninitalizedAkeylessProvider)
  364. }
  365. secret, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: ref.GetRemoteKey()})
  366. if errors.Is(err, ErrItemNotExists) {
  367. return false, nil
  368. }
  369. if err != nil {
  370. return false, err
  371. }
  372. if ref.GetProperty() == "" {
  373. return true, nil
  374. }
  375. var secretMap map[string]any
  376. err = json.Unmarshal(secret, &secretMap)
  377. if err != nil {
  378. return false, err
  379. }
  380. _, ok := secretMap[ref.GetProperty()]
  381. return ok, nil
  382. }
  383. func initMapIfNotExist(psd esv1.PushSecretData, secretMapSize int) map[string]any {
  384. mapSize := 1
  385. if psd.GetProperty() == "" {
  386. mapSize = secretMapSize
  387. }
  388. return make(map[string]any, mapSize)
  389. }
  390. func (a *Akeyless) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1.PushSecretData) error {
  391. if utils.IsNil(a.Client) {
  392. return errors.New(errUninitalizedAkeylessProvider)
  393. }
  394. ctx, err := a.contextWithToken(ctx)
  395. if err != nil {
  396. return err
  397. }
  398. secretRemote, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: psd.GetRemoteKey()})
  399. isNotExists := errors.Is(err, ErrItemNotExists)
  400. if err != nil && !isNotExists {
  401. return err
  402. }
  403. var data map[string]any
  404. if isNotExists {
  405. data = initMapIfNotExist(psd, len(secret.Data))
  406. err = nil
  407. } else {
  408. err = json.Unmarshal(secretRemote, &data)
  409. }
  410. if err != nil {
  411. return err
  412. }
  413. if psd.GetProperty() == "" {
  414. for k, v := range secret.Data {
  415. data[k] = string(v)
  416. }
  417. } else if v, ok := secret.Data[psd.GetSecretKey()]; ok {
  418. data[psd.GetProperty()] = string(v)
  419. }
  420. dataByte, err := json.Marshal(data)
  421. if err != nil {
  422. return err
  423. }
  424. if bytes.Equal(dataByte, secretRemote) {
  425. return nil
  426. }
  427. if isNotExists {
  428. return a.Client.CreateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
  429. }
  430. return a.Client.UpdateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
  431. }
  432. func (a *Akeyless) DeleteSecret(ctx context.Context, psr esv1.PushSecretRemoteRef) error {
  433. if utils.IsNil(a.Client) {
  434. return errors.New(errUninitalizedAkeylessProvider)
  435. }
  436. ctx, err := a.contextWithToken(ctx)
  437. if err != nil {
  438. return err
  439. }
  440. item, err := a.Client.DescribeItem(ctx, psr.GetRemoteKey())
  441. if err != nil {
  442. return err
  443. }
  444. if item == nil || item.ItemTags == nil || !slices.Contains(*item.ItemTags, extSecretManagedTag) {
  445. return nil
  446. }
  447. if psr.GetProperty() == "" {
  448. err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
  449. return err
  450. }
  451. secret, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: psr.GetRemoteKey()})
  452. if err != nil {
  453. return err
  454. }
  455. var secretMap map[string]any
  456. err = json.Unmarshal(secret, &secretMap)
  457. if err != nil {
  458. return err
  459. }
  460. delete(secretMap, psr.GetProperty())
  461. if len(secretMap) == 0 {
  462. err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
  463. return err
  464. }
  465. byteSecretMap, err := json.Marshal(secretMap)
  466. if err != nil {
  467. return err
  468. }
  469. err = a.Client.UpdateSecret(ctx, psr.GetRemoteKey(), string(byteSecretMap))
  470. return err
  471. }
  472. func (a *akeylessBase) getAkeylessHTTPClient(ctx context.Context, provider *esv1.AkeylessProvider) (*http.Client, error) {
  473. client := &http.Client{Timeout: 30 * time.Second}
  474. if len(provider.CABundle) == 0 && provider.CAProvider == nil {
  475. return client, nil
  476. }
  477. cert, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
  478. StoreKind: a.storeKind,
  479. Client: a.kube,
  480. Namespace: a.namespace,
  481. CABundle: provider.CABundle,
  482. CAProvider: provider.CAProvider,
  483. })
  484. if err != nil {
  485. return nil, err
  486. }
  487. caCertPool := x509.NewCertPool()
  488. ok := caCertPool.AppendCertsFromPEM(cert)
  489. if !ok {
  490. return nil, errors.New("failed to append caBundle")
  491. }
  492. tlsConf := &tls.Config{
  493. RootCAs: caCertPool,
  494. MinVersion: tls.VersionTLS12,
  495. }
  496. client.Transport = &http.Transport{TLSClientConfig: tlsConf}
  497. return client, nil
  498. }