akeyless.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. /*
  2. Copyright © The ESO Authors
  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 provides integration with Akeyless Vault for secrets management.
  14. package akeyless
  15. import (
  16. "bytes"
  17. "context"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "encoding/json"
  21. "errors"
  22. "fmt"
  23. "net/http"
  24. "net/url"
  25. "slices"
  26. "strconv"
  27. "strings"
  28. "time"
  29. "github.com/akeylesslabs/akeyless-go/v4"
  30. "github.com/tidwall/gjson"
  31. corev1 "k8s.io/api/core/v1"
  32. "k8s.io/client-go/kubernetes"
  33. typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  34. "sigs.k8s.io/controller-runtime/pkg/client"
  35. ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
  36. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  37. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  38. "github.com/external-secrets/external-secrets/runtime/esutils"
  39. "github.com/external-secrets/external-secrets/runtime/find"
  40. )
  41. // Ctx is a type used for context keys in Akeyless provider implementations.
  42. type Ctx string
  43. const (
  44. defaultAPIUrl = "https://api.akeyless.io"
  45. extSecretManagedTag = "k8s-external-secrets"
  46. aKeylessToken Ctx = "AKEYLESS_TOKEN"
  47. )
  48. // https://github.com/external-secrets/external-secrets/issues/644
  49. var _ esv1.SecretsClient = &Akeyless{}
  50. var _ esv1.Provider = &Provider{}
  51. // Provider satisfies the provider interface.
  52. type Provider struct{}
  53. // akeylessBase satisfies the provider.SecretsClient interface.
  54. type akeylessBase struct {
  55. kube client.Client
  56. store esv1.GenericStore
  57. storeKind string
  58. corev1 typedcorev1.CoreV1Interface
  59. namespace string
  60. akeylessGwAPIURL string
  61. RestAPI *akeyless.V2ApiService
  62. }
  63. // Akeyless represents a client for the Akeyless Vault service.
  64. type Akeyless struct {
  65. Client akeylessVaultInterface
  66. url string
  67. }
  68. // Item represents an item in the Akeyless Vault.
  69. type Item struct {
  70. ItemName string `json:"item_name"`
  71. ItemType string `json:"item_type"`
  72. LastVersion int32 `json:"last_version"`
  73. }
  74. type akeylessVaultInterface interface {
  75. GetSecretByType(ctx context.Context, secretName string, version int32) (string, error)
  76. TokenFromSecretRef(ctx context.Context) (string, error)
  77. ListSecrets(ctx context.Context, path, tag string) ([]string, error)
  78. DescribeItem(ctx context.Context, itemName string) (*akeyless.Item, error)
  79. CreateSecret(ctx context.Context, remoteKey, data string) error
  80. UpdateSecret(ctx context.Context, remoteKey, data string) error
  81. DeleteSecret(ctx context.Context, remoteKey string) error
  82. }
  83. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  84. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  85. return esv1.SecretStoreReadOnly
  86. }
  87. // NewClient constructs a new secrets client based on the provided store.
  88. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
  89. // controller-runtime/client does not support TokenRequest or other subresource APIs
  90. // so we need to construct our own client and use it to fetch tokens
  91. // (for Kubernetes service account token auth)
  92. restCfg, err := ctrlcfg.GetConfig()
  93. if err != nil {
  94. return nil, err
  95. }
  96. clientset, err := kubernetes.NewForConfig(restCfg)
  97. if err != nil {
  98. return nil, err
  99. }
  100. return newClient(ctx, store, kube, clientset.CoreV1(), namespace)
  101. }
  102. // ValidateStore validates the configuration of the Akeyless provider in the store.
  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. parsedURL, err := url.Parse(*akeylessGWApiURL)
  109. if err != nil {
  110. return nil, errors.New(errInvalidAkeylessURL)
  111. }
  112. if parsedURL.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 := esutils.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 := esutils.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 := esutils.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 = esutils.ValidateSecretSelector(store, accessType)
  149. if err != nil {
  150. return nil, err
  151. }
  152. accessTypeParam := akeylessSpec.Auth.SecretRef.AccessTypeParam
  153. err = esutils.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. // Close closes the Akeyless client connection.
  205. func (a *Akeyless) Close(_ context.Context) error {
  206. return nil
  207. }
  208. // Validate validates the Akeyless connection by testing network connectivity.
  209. func (a *Akeyless) Validate() (esv1.ValidationResult, error) {
  210. timeout := 15 * time.Second
  211. serviceURL := a.url
  212. if err := esutils.NetworkValidate(serviceURL, timeout); err != nil {
  213. return esv1.ValidationResultError, err
  214. }
  215. return esv1.ValidationResultReady, nil
  216. }
  217. // GetSecret retrieves a secret with the secret name defined in ref.Name.
  218. // Implements store.Client.GetSecret Interface.
  219. func (a *Akeyless) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  220. if esutils.IsNil(a.Client) {
  221. return nil, errors.New(errUninitalizedAkeylessProvider)
  222. }
  223. ctx, err := a.contextWithToken(ctx)
  224. if err != nil {
  225. return nil, err
  226. }
  227. version := int32(0)
  228. if ref.Version != "" {
  229. i, err := strconv.ParseInt(ref.Version, 10, 32)
  230. if err == nil {
  231. version = int32(i)
  232. }
  233. }
  234. value, err := a.Client.GetSecretByType(ctx, ref.Key, version)
  235. if err != nil {
  236. return nil, err
  237. }
  238. if ref.Property == "" {
  239. if value != "" {
  240. return []byte(value), nil
  241. }
  242. return nil, fmt.Errorf("invalid value received, found no value string : %s", ref.Key)
  243. }
  244. // We need to search if a given key with a . exists before using gjson operations.
  245. idx := strings.Index(ref.Property, ".")
  246. if idx > -1 {
  247. refProperty := strings.ReplaceAll(ref.Property, ".", "\\.")
  248. val := gjson.Get(value, refProperty)
  249. if val.Exists() {
  250. return []byte(val.String()), nil
  251. }
  252. }
  253. val := gjson.Get(value, ref.Property)
  254. if !val.Exists() {
  255. return nil, fmt.Errorf("key %s does not exist in value %s", ref.Property, ref.Key)
  256. }
  257. return []byte(val.String()), nil
  258. }
  259. // GetAllSecrets Implements store.Client.GetAllSecrets Interface.
  260. // Retrieves all secrets with defined in ref.Name or tags.
  261. func (a *Akeyless) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  262. if esutils.IsNil(a.Client) {
  263. return nil, errors.New(errUninitalizedAkeylessProvider)
  264. }
  265. ctx, err := a.contextWithToken(ctx)
  266. if err != nil {
  267. return nil, err
  268. }
  269. searchPath := ""
  270. if ref.Path != nil {
  271. searchPath = *ref.Path
  272. if !strings.HasPrefix(searchPath, "/") {
  273. searchPath = "/" + searchPath
  274. }
  275. if !strings.HasSuffix(searchPath, "/") {
  276. searchPath += "/"
  277. }
  278. }
  279. if ref.Name != nil {
  280. return a.findSecretsFromName(ctx, searchPath, *ref.Name)
  281. }
  282. if len(ref.Tags) > 0 {
  283. return a.getSecrets(ctx, searchPath, ref.Tags)
  284. }
  285. return nil, errors.New("unexpected find operator")
  286. }
  287. func (a *Akeyless) getSecrets(ctx context.Context, searchPath string, tags map[string]string) (map[string][]byte, error) {
  288. var potentialSecretsName []string
  289. for _, v := range tags {
  290. potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, v)
  291. if err != nil {
  292. return nil, err
  293. }
  294. if len(potentialSecrets) > 0 {
  295. potentialSecretsName = append(potentialSecretsName, potentialSecrets...)
  296. }
  297. }
  298. if len(potentialSecretsName) == 0 {
  299. return nil, nil
  300. }
  301. secrets := make(map[string][]byte)
  302. for _, name := range potentialSecretsName {
  303. secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
  304. if err != nil {
  305. return nil, err
  306. }
  307. if secretValue != "" {
  308. secrets[name] = []byte(secretValue)
  309. }
  310. }
  311. return secrets, nil
  312. }
  313. func (a *Akeyless) findSecretsFromName(ctx context.Context, searchPath string, ref esv1.FindName) (map[string][]byte, error) {
  314. potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, "")
  315. if err != nil {
  316. return nil, err
  317. }
  318. if len(potentialSecrets) == 0 {
  319. return nil, nil
  320. }
  321. secrets := make(map[string][]byte)
  322. matcher, err := find.New(ref)
  323. if err != nil {
  324. return nil, err
  325. }
  326. for _, name := range potentialSecrets {
  327. ok := matcher.MatchName(name)
  328. if ok {
  329. secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
  330. if err != nil {
  331. return nil, err
  332. }
  333. if secretValue != "" {
  334. secrets[name] = []byte(secretValue)
  335. }
  336. }
  337. }
  338. return secrets, nil
  339. }
  340. // GetSecretMap implements store.Client.GetSecretMap Interface.
  341. // New version of GetSecretMap.
  342. func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  343. if esutils.IsNil(a.Client) {
  344. return nil, errors.New(errUninitalizedAkeylessProvider)
  345. }
  346. val, err := a.GetSecret(ctx, ref)
  347. if err != nil {
  348. return nil, err
  349. }
  350. // Maps the json data to a string:string map
  351. kv := make(map[string]string)
  352. err = json.Unmarshal(val, &kv)
  353. if err != nil {
  354. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  355. }
  356. // Converts values in K:V pairs into bytes, while leaving keys as strings
  357. secretData := make(map[string][]byte)
  358. for k, v := range kv {
  359. secretData[k] = []byte(v)
  360. }
  361. return secretData, nil
  362. }
  363. // SecretExists checks if a secret exists in Akeyless Vault at the specified remote reference.
  364. func (a *Akeyless) SecretExists(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
  365. if esutils.IsNil(a.Client) {
  366. return false, errors.New(errUninitalizedAkeylessProvider)
  367. }
  368. secret, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: ref.GetRemoteKey()})
  369. if errors.Is(err, ErrItemNotExists) {
  370. return false, nil
  371. }
  372. if err != nil {
  373. return false, err
  374. }
  375. if ref.GetProperty() == "" {
  376. return true, nil
  377. }
  378. var secretMap map[string]any
  379. err = json.Unmarshal(secret, &secretMap)
  380. if err != nil {
  381. // Do not return the raw error as json.Unmarshal errors may contain
  382. // sensitive secret data in the error message
  383. return false, errors.New("failed to unmarshal secret: invalid JSON format")
  384. }
  385. _, ok := secretMap[ref.GetProperty()]
  386. return ok, nil
  387. }
  388. func initMapIfNotExist(psd esv1.PushSecretData, secretMapSize int) map[string]any {
  389. mapSize := 1
  390. if psd.GetProperty() == "" {
  391. mapSize = secretMapSize
  392. }
  393. return make(map[string]any, mapSize)
  394. }
  395. // PushSecret pushes a Kubernetes secret to Akeyless Vault using the provided data.
  396. func (a *Akeyless) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1.PushSecretData) error {
  397. if esutils.IsNil(a.Client) {
  398. return errors.New(errUninitalizedAkeylessProvider)
  399. }
  400. ctx, err := a.contextWithToken(ctx)
  401. if err != nil {
  402. return err
  403. }
  404. secretRemote, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: psd.GetRemoteKey()})
  405. isNotExists := errors.Is(err, ErrItemNotExists)
  406. if err != nil && !isNotExists {
  407. return err
  408. }
  409. var data map[string]any
  410. if isNotExists {
  411. data = initMapIfNotExist(psd, len(secret.Data))
  412. err = nil
  413. } else {
  414. err = json.Unmarshal(secretRemote, &data)
  415. }
  416. if err != nil {
  417. // Do not return the raw error as json.Unmarshal errors may contain
  418. // sensitive secret data in the error message
  419. return errors.New("failed to unmarshal remote secret: invalid JSON format")
  420. }
  421. if psd.GetProperty() == "" {
  422. for k, v := range secret.Data {
  423. data[k] = string(v)
  424. }
  425. } else if v, ok := secret.Data[psd.GetSecretKey()]; ok {
  426. data[psd.GetProperty()] = string(v)
  427. }
  428. dataByte, err := json.Marshal(data)
  429. if err != nil {
  430. return err
  431. }
  432. if bytes.Equal(dataByte, secretRemote) {
  433. return nil
  434. }
  435. if isNotExists {
  436. return a.Client.CreateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
  437. }
  438. return a.Client.UpdateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
  439. }
  440. // DeleteSecret deletes a secret from Akeyless Vault at the specified remote reference.
  441. func (a *Akeyless) DeleteSecret(ctx context.Context, psr esv1.PushSecretRemoteRef) error {
  442. if esutils.IsNil(a.Client) {
  443. return errors.New(errUninitalizedAkeylessProvider)
  444. }
  445. ctx, err := a.contextWithToken(ctx)
  446. if err != nil {
  447. return err
  448. }
  449. item, err := a.Client.DescribeItem(ctx, psr.GetRemoteKey())
  450. if err != nil {
  451. return err
  452. }
  453. if item == nil || item.ItemTags == nil || !slices.Contains(*item.ItemTags, extSecretManagedTag) {
  454. return nil
  455. }
  456. if psr.GetProperty() == "" {
  457. err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
  458. return err
  459. }
  460. secret, err := a.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: psr.GetRemoteKey()})
  461. if err != nil {
  462. return err
  463. }
  464. var secretMap map[string]any
  465. err = json.Unmarshal(secret, &secretMap)
  466. if err != nil {
  467. // Do not return the raw error as json.Unmarshal errors may contain
  468. // sensitive secret data in the error message
  469. return errors.New("failed to unmarshal secret for deletion: invalid JSON format")
  470. }
  471. delete(secretMap, psr.GetProperty())
  472. if len(secretMap) == 0 {
  473. err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
  474. return err
  475. }
  476. byteSecretMap, err := json.Marshal(secretMap)
  477. if err != nil {
  478. return err
  479. }
  480. err = a.Client.UpdateSecret(ctx, psr.GetRemoteKey(), string(byteSecretMap))
  481. return err
  482. }
  483. func (a *akeylessBase) getAkeylessHTTPClient(ctx context.Context, provider *esv1.AkeylessProvider) (*http.Client, error) {
  484. client := &http.Client{Timeout: 30 * time.Second}
  485. if len(provider.CABundle) == 0 && provider.CAProvider == nil {
  486. return client, nil
  487. }
  488. cert, err := esutils.FetchCACertFromSource(ctx, esutils.CreateCertOpts{
  489. StoreKind: a.storeKind,
  490. Client: a.kube,
  491. Namespace: a.namespace,
  492. CABundle: provider.CABundle,
  493. CAProvider: provider.CAProvider,
  494. })
  495. if err != nil {
  496. return nil, err
  497. }
  498. caCertPool := x509.NewCertPool()
  499. ok := caCertPool.AppendCertsFromPEM(cert)
  500. if !ok {
  501. return nil, errors.New("failed to append caBundle")
  502. }
  503. tlsConf := &tls.Config{
  504. RootCAs: caCertPool,
  505. MinVersion: tls.VersionTLS12,
  506. }
  507. client.Transport = &http.Transport{TLSClientConfig: tlsConf}
  508. return client, nil
  509. }
  510. // NewProvider creates a new Provider instance.
  511. func NewProvider() esv1.Provider {
  512. return &Provider{}
  513. }
  514. // ProviderSpec returns the provider specification for registration.
  515. func ProviderSpec() *esv1.SecretStoreProvider {
  516. return &esv1.SecretStoreProvider{
  517. Akeyless: &esv1.AkeylessProvider{},
  518. }
  519. }
  520. // MaintenanceStatus returns the maintenance status of the provider.
  521. func MaintenanceStatus() esv1.MaintenanceStatus {
  522. return esv1.MaintenanceStatusMaintained
  523. }