provider.go 14 KB

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