provider.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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 infisical
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. infisicalSdk "github.com/infisical/go-sdk"
  19. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  20. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  23. "github.com/external-secrets/external-secrets/pkg/metrics"
  24. "github.com/external-secrets/external-secrets/pkg/provider/infisical/constants"
  25. "github.com/external-secrets/external-secrets/pkg/utils"
  26. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  27. )
  28. const (
  29. machineIdentityLoginViaUniversalAuth = "MachineIdentityLoginViaUniversalAuth"
  30. machineIdentityLoginViaAzureAuth = "MachineIdentityLoginViaAzureAuth"
  31. machineIdentityLoginViaGcpIdTokenAuth = "MachineIdentityLoginViaGcpIdTokenAuth"
  32. machineIdentityLoginViaGcpServiceAccountAuth = "MachineIdentityLoginViaGcpServiceAccountAuth"
  33. machineIdentityLoginViaJwtAuth = "MachineIdentityLoginViaJwtAuth"
  34. machineIdentityLoginViaLdapAuth = "MachineIdentityLoginViaLdapAuth"
  35. machineIdentityLoginViaOciAuth = "MachineIdentityLoginViaOciAuth"
  36. machineIdentityLoginViaKubernetesAuth = "MachineIdentityLoginViaKubernetesAuth"
  37. machineIdentityLoginViaAwsAuth = "MachineIdentityLoginViaAwsAuth"
  38. machineIdentityLoginViaTokenAuth = "MachineIdentityLoginViaTokenAuth"
  39. revokeAccessToken = "RevokeAccessToken"
  40. )
  41. const errSecretDataFormat = "failed to get secret data identityId %w"
  42. type Provider struct {
  43. cancelSdkClient context.CancelFunc
  44. sdkClient infisicalSdk.InfisicalClientInterface
  45. apiScope *InfisicalClientScope
  46. authMethod string
  47. }
  48. type InfisicalClientScope struct {
  49. EnvironmentSlug string
  50. ProjectSlug string
  51. Recursive bool
  52. SecretPath string
  53. ExpandSecretReferences bool
  54. }
  55. // https://github.com/external-secrets/external-secrets/issues/644
  56. var _ esv1.SecretsClient = &Provider{}
  57. var _ esv1.Provider = &Provider{}
  58. func init() {
  59. esv1.Register(&Provider{}, &esv1.SecretStoreProvider{
  60. Infisical: &esv1.InfisicalProvider{},
  61. }, esv1.MaintenanceStatusMaintained)
  62. }
  63. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  64. return esv1.SecretStoreReadOnly
  65. }
  66. func performUniversalAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  67. universalAuthCredentials := infisicalSpec.Auth.UniversalAuthCredentials
  68. clientID, err := GetStoreSecretData(ctx, store, kube, namespace, universalAuthCredentials.ClientID)
  69. if err != nil {
  70. return err
  71. }
  72. clientSecret, err := GetStoreSecretData(ctx, store, kube, namespace, universalAuthCredentials.ClientSecret)
  73. if err != nil {
  74. return err
  75. }
  76. _, err = sdkClient.Auth().UniversalAuthLogin(clientID, clientSecret)
  77. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaUniversalAuth, err)
  78. if err != nil {
  79. return fmt.Errorf("failed to authenticate via universal auth %w", err)
  80. }
  81. return nil
  82. }
  83. func performAzureAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  84. azureAuthCredentials := infisicalSpec.Auth.AzureAuthCredentials
  85. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, azureAuthCredentials.IdentityID)
  86. if err != nil {
  87. return fmt.Errorf("failed to get secret data id %w", err)
  88. }
  89. resource := ""
  90. if azureAuthCredentials.Resource.Name != "" {
  91. resource, err = GetStoreSecretData(ctx, store, kube, namespace, azureAuthCredentials.Resource)
  92. if err != nil {
  93. return fmt.Errorf("failed to get secret data resource %w", err)
  94. }
  95. }
  96. _, err = sdkClient.Auth().AzureAuthLogin(identityID, resource)
  97. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaAzureAuth, err)
  98. if err != nil {
  99. return fmt.Errorf("failed to authenticate via azure auth %w", err)
  100. }
  101. return nil
  102. }
  103. func performGcpIdTokenAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  104. gcpIdTokenAuthCredentials := infisicalSpec.Auth.GcpIdTokenAuthCredentials
  105. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, gcpIdTokenAuthCredentials.IdentityID)
  106. if err != nil {
  107. return fmt.Errorf(errSecretDataFormat, err)
  108. }
  109. _, err = sdkClient.Auth().GcpIdTokenAuthLogin(identityID)
  110. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaGcpIdTokenAuth, err)
  111. if err != nil {
  112. return fmt.Errorf("failed to authenticate via gcp id token auth %w", err)
  113. }
  114. return nil
  115. }
  116. func performGcpIamAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  117. gcpIamAuthCredentials := infisicalSpec.Auth.GcpIamAuthCredentials
  118. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, gcpIamAuthCredentials.IdentityID)
  119. if err != nil {
  120. return fmt.Errorf(errSecretDataFormat, err)
  121. }
  122. serviceAccountKeyFilePath, err := GetStoreSecretData(ctx, store, kube, namespace, gcpIamAuthCredentials.ServiceAccountKeyFilePath)
  123. if err != nil {
  124. return fmt.Errorf("failed to get secret data serviceAccountKeyFilePath %w", err)
  125. }
  126. _, err = sdkClient.Auth().GcpIamAuthLogin(identityID, serviceAccountKeyFilePath)
  127. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaGcpServiceAccountAuth, err)
  128. if err != nil {
  129. return fmt.Errorf("failed to authenticate via gcp iam auth %w", err)
  130. }
  131. return nil
  132. }
  133. func performJwtAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  134. jwtAuthCredentials := infisicalSpec.Auth.JwtAuthCredentials
  135. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, jwtAuthCredentials.IdentityID)
  136. if err != nil {
  137. return fmt.Errorf(errSecretDataFormat, err)
  138. }
  139. jwt, err := GetStoreSecretData(ctx, store, kube, namespace, jwtAuthCredentials.JWT)
  140. if err != nil {
  141. return fmt.Errorf("failed to get secret data jwt %w", err)
  142. }
  143. _, err = sdkClient.Auth().JwtAuthLogin(identityID, jwt)
  144. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaJwtAuth, err)
  145. if err != nil {
  146. return fmt.Errorf("failed to authenticate via jwt auth %w", err)
  147. }
  148. return nil
  149. }
  150. func performLdapAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  151. ldapAuthCredentials := infisicalSpec.Auth.LdapAuthCredentials
  152. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, ldapAuthCredentials.IdentityID)
  153. if err != nil {
  154. return fmt.Errorf(errSecretDataFormat, err)
  155. }
  156. ldapPassword, err := GetStoreSecretData(ctx, store, kube, namespace, ldapAuthCredentials.LDAPPassword)
  157. if err != nil {
  158. return fmt.Errorf("failed to get secret data ldapPassword %w", err)
  159. }
  160. ldapUsername, err := GetStoreSecretData(ctx, store, kube, namespace, ldapAuthCredentials.LDAPUsername)
  161. if err != nil {
  162. return fmt.Errorf("failed to get secret data ldapUsername %w", err)
  163. }
  164. _, err = sdkClient.Auth().LdapAuthLogin(identityID, ldapPassword, ldapUsername)
  165. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaLdapAuth, err)
  166. if err != nil {
  167. return fmt.Errorf("failed to authenticate via ldap auth %w", err)
  168. }
  169. return nil
  170. }
  171. func performOciAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  172. ociAuthCredentials := infisicalSpec.Auth.OciAuthCredentials
  173. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.IdentityID)
  174. if err != nil {
  175. return fmt.Errorf(errSecretDataFormat, err)
  176. }
  177. privateKey, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.PrivateKey)
  178. if err != nil {
  179. return fmt.Errorf("failed to get secret data privateKey %w", err)
  180. }
  181. var privateKeyPassphrase *string = nil
  182. if ociAuthCredentials.PrivateKeyPassphrase.Name != "" {
  183. passphrase, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.PrivateKeyPassphrase)
  184. if err != nil {
  185. return fmt.Errorf("failed to get secret data privateKeyPassphrase %w", err)
  186. }
  187. privateKeyPassphrase = &passphrase
  188. }
  189. fingerprint, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.Fingerprint)
  190. if err != nil {
  191. return fmt.Errorf("failed to get secret data fingerprint %w", err)
  192. }
  193. userID, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.UserID)
  194. if err != nil {
  195. return fmt.Errorf("failed to get secret data userId %w", err)
  196. }
  197. tenancyID, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.TenancyID)
  198. if err != nil {
  199. return fmt.Errorf("failed to get secret data tenancyId %w", err)
  200. }
  201. region, err := GetStoreSecretData(ctx, store, kube, namespace, ociAuthCredentials.Region)
  202. if err != nil {
  203. return fmt.Errorf("failed to get secret data region %w", err)
  204. }
  205. _, err = sdkClient.Auth().OciAuthLogin(infisicalSdk.OciAuthLoginOptions{
  206. IdentityID: identityID,
  207. PrivateKey: privateKey,
  208. Passphrase: privateKeyPassphrase,
  209. Fingerprint: fingerprint,
  210. UserID: userID,
  211. TenancyID: tenancyID,
  212. Region: region,
  213. })
  214. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaOciAuth, err)
  215. if err != nil {
  216. return fmt.Errorf("failed to authenticate via oci auth %w", err)
  217. }
  218. return nil
  219. }
  220. func performKubernetesAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  221. kubernetesAuthCredentials := infisicalSpec.Auth.KubernetesAuthCredentials
  222. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, kubernetesAuthCredentials.IdentityID)
  223. if err != nil {
  224. return fmt.Errorf(errSecretDataFormat, err)
  225. }
  226. serviceAccountTokenPath := ""
  227. if kubernetesAuthCredentials.ServiceAccountTokenPath.Name != "" {
  228. serviceAccountTokenPath, err = GetStoreSecretData(ctx, store, kube, namespace, kubernetesAuthCredentials.ServiceAccountTokenPath)
  229. if err != nil {
  230. return fmt.Errorf("failed to get secret data serviceAccountTokenPath %w", err)
  231. }
  232. }
  233. _, err = sdkClient.Auth().KubernetesAuthLogin(identityID, serviceAccountTokenPath)
  234. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaKubernetesAuth, err)
  235. if err != nil {
  236. return fmt.Errorf("failed to authenticate via kubernetes auth %w", err)
  237. }
  238. return nil
  239. }
  240. func performAwsAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  241. awsAuthCredentials := infisicalSpec.Auth.AwsAuthCredentials
  242. identityID, err := GetStoreSecretData(ctx, store, kube, namespace, awsAuthCredentials.IdentityID)
  243. if err != nil {
  244. return fmt.Errorf(errSecretDataFormat, err)
  245. }
  246. _, err = sdkClient.Auth().AwsIamAuthLogin(identityID)
  247. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaAwsAuth, err)
  248. if err != nil {
  249. return fmt.Errorf("failed to authenticate via aws auth %w", err)
  250. }
  251. return nil
  252. }
  253. func performTokenAuthLogin(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error {
  254. tokenAuthCredentials := infisicalSpec.Auth.TokenAuthCredentials
  255. accessToken, err := GetStoreSecretData(ctx, store, kube, namespace, tokenAuthCredentials.AccessToken)
  256. if err != nil {
  257. return fmt.Errorf(errSecretDataFormat, err)
  258. }
  259. sdkClient.Auth().SetAccessToken(accessToken)
  260. metrics.ObserveAPICall(constants.ProviderName, machineIdentityLoginViaTokenAuth, err)
  261. return nil
  262. }
  263. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  264. storeSpec := store.GetSpec()
  265. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Infisical == nil {
  266. return nil, errors.New("invalid infisical store")
  267. }
  268. infisicalSpec := storeSpec.Provider.Infisical
  269. ctx, cancelSdkClient := context.WithCancel(ctx)
  270. sdkClient := infisicalSdk.NewInfisicalClient(ctx, infisicalSdk.Config{
  271. SiteUrl: infisicalSpec.HostAPI,
  272. })
  273. secretPath := infisicalSpec.SecretsScope.SecretsPath
  274. if secretPath == "" {
  275. secretPath = "/"
  276. }
  277. var loginFn func(ctx context.Context, store esv1.GenericStore, infisicalSpec *esv1.InfisicalProvider, sdkClient infisicalSdk.InfisicalClientInterface, kube kclient.Client, namespace string) error
  278. var authMethod string
  279. switch {
  280. case infisicalSpec.Auth.UniversalAuthCredentials != nil:
  281. loginFn = performUniversalAuthLogin
  282. authMethod = machineIdentityLoginViaUniversalAuth
  283. case infisicalSpec.Auth.AzureAuthCredentials != nil:
  284. loginFn = performAzureAuthLogin
  285. authMethod = machineIdentityLoginViaAzureAuth
  286. case infisicalSpec.Auth.GcpIdTokenAuthCredentials != nil:
  287. loginFn = performGcpIdTokenAuthLogin
  288. authMethod = machineIdentityLoginViaGcpIdTokenAuth
  289. case infisicalSpec.Auth.GcpIamAuthCredentials != nil:
  290. loginFn = performGcpIamAuthLogin
  291. authMethod = machineIdentityLoginViaGcpServiceAccountAuth
  292. case infisicalSpec.Auth.JwtAuthCredentials != nil:
  293. loginFn = performJwtAuthLogin
  294. authMethod = machineIdentityLoginViaJwtAuth
  295. case infisicalSpec.Auth.LdapAuthCredentials != nil:
  296. loginFn = performLdapAuthLogin
  297. authMethod = machineIdentityLoginViaLdapAuth
  298. case infisicalSpec.Auth.OciAuthCredentials != nil:
  299. loginFn = performOciAuthLogin
  300. authMethod = machineIdentityLoginViaOciAuth
  301. case infisicalSpec.Auth.KubernetesAuthCredentials != nil:
  302. loginFn = performKubernetesAuthLogin
  303. authMethod = machineIdentityLoginViaKubernetesAuth
  304. case infisicalSpec.Auth.AwsAuthCredentials != nil:
  305. loginFn = performAwsAuthLogin
  306. authMethod = machineIdentityLoginViaAwsAuth
  307. case infisicalSpec.Auth.TokenAuthCredentials != nil:
  308. loginFn = performTokenAuthLogin
  309. authMethod = machineIdentityLoginViaTokenAuth
  310. default:
  311. cancelSdkClient()
  312. return nil, errors.New("authentication method not found")
  313. }
  314. if err := loginFn(ctx, store, infisicalSpec, sdkClient, kube, namespace); err != nil {
  315. cancelSdkClient()
  316. return nil, err
  317. }
  318. return &Provider{
  319. cancelSdkClient: cancelSdkClient,
  320. sdkClient: sdkClient,
  321. apiScope: &InfisicalClientScope{
  322. EnvironmentSlug: infisicalSpec.SecretsScope.EnvironmentSlug,
  323. ProjectSlug: infisicalSpec.SecretsScope.ProjectSlug,
  324. Recursive: infisicalSpec.SecretsScope.Recursive,
  325. SecretPath: secretPath,
  326. ExpandSecretReferences: infisicalSpec.SecretsScope.ExpandSecretReferences,
  327. },
  328. authMethod: authMethod,
  329. }, nil
  330. }
  331. func (p *Provider) Close(ctx context.Context) error {
  332. p.cancelSdkClient()
  333. // Don't revoke token if token auth was used
  334. if p.authMethod == machineIdentityLoginViaTokenAuth {
  335. return nil
  336. }
  337. err := p.sdkClient.Auth().RevokeAccessToken()
  338. metrics.ObserveAPICall(constants.ProviderName, revokeAccessToken, err)
  339. return err
  340. }
  341. func GetStoreSecretData(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string, secret esmeta.SecretKeySelector) (string, error) {
  342. secretRef := esmeta.SecretKeySelector{
  343. Name: secret.Name,
  344. Key: secret.Key,
  345. }
  346. if secret.Namespace != nil {
  347. secretRef.Namespace = secret.Namespace
  348. }
  349. secretData, err := resolvers.SecretKeyRef(ctx, kube, store.GetObjectKind().GroupVersionKind().Kind, namespace, &secretRef)
  350. if err != nil {
  351. return "", err
  352. }
  353. return secretData, nil
  354. }
  355. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  356. storeSpec := store.GetSpec()
  357. infisicalStoreSpec := storeSpec.Provider.Infisical
  358. if infisicalStoreSpec == nil {
  359. return nil, errors.New("invalid infisical store")
  360. }
  361. if infisicalStoreSpec.SecretsScope.EnvironmentSlug == "" || infisicalStoreSpec.SecretsScope.ProjectSlug == "" {
  362. return nil, errors.New("secretsScope.projectSlug and secretsScope.environmentSlug cannot be empty")
  363. }
  364. if infisicalStoreSpec.Auth.UniversalAuthCredentials != nil {
  365. uaCredential := infisicalStoreSpec.Auth.UniversalAuthCredentials
  366. // to validate reference authentication
  367. err := utils.ValidateReferentSecretSelector(store, uaCredential.ClientID)
  368. if err != nil {
  369. return nil, err
  370. }
  371. err = utils.ValidateReferentSecretSelector(store, uaCredential.ClientSecret)
  372. if err != nil {
  373. return nil, err
  374. }
  375. if uaCredential.ClientID.Key == "" || uaCredential.ClientSecret.Key == "" {
  376. return nil, errors.New("universalAuthCredentials.clientId and universalAuthCredentials.clientSecret cannot be empty")
  377. }
  378. }
  379. return nil, nil
  380. }