provider.go 17 KB

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