provider.go 17 KB

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