Просмотр исходного кода

Infisical provider (#3477)

* feat: added crds for infisical provider

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: implemented infisical provider logic

Signed-off-by: = <akhilmhdh@gmail.com>

* fix: resolved broken doc building due to vault doc error

Signed-off-by: = <akhilmhdh@gmail.com>

* docs: added doc for infisical provider

Signed-off-by: = <akhilmhdh@gmail.com>

* docs: fixed a warning in mkdocs on link

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: resolved all lint issues

Signed-off-by: = <akhilmhdh@gmail.com>

* doc: removed k8s auth release banner from infisical doc

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: added support for property to infisical provider

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: removed auth type and made implicit ordering of authentication based on feedback

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: support for referent authentication

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: added error for tag not supported in find

Signed-off-by: = <akhilmhdh@gmail.com>

* fix: resolved failing build

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: updated doc and added stability matrix for infisical

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: switched to less error prone use and revoke token strategy and added validate interface logic

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: code lint issue fixes

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: resolved review comments for infisical client

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: improved test cases and resolved sonar issues

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: resolved sonar suggestions

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: resolved sonar suggestions for test const ids

Signed-off-by: = <akhilmhdh@gmail.com>

* feat: store changes to assertError

Signed-off-by: = <akhilmhdh@gmail.com>

---------

Signed-off-by: = <akhilmhdh@gmail.com>
Akhil Mohan 2 лет назад
Родитель
Сommit
ace1ff595f

+ 53 - 0
apis/externalsecrets/v1beta1/secretsstore_infisical_types.go

@@ -0,0 +1,53 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1beta1
+
+import (
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+type UniversalAuthCredentials struct {
+	// +kubebuilder:validation:Required
+	ClientID esmeta.SecretKeySelector `json:"clientId"`
+	// +kubebuilder:validation:Required
+	ClientSecret esmeta.SecretKeySelector `json:"clientSecret"`
+}
+
+type InfisicalAuth struct {
+	// +optional
+	UniversalAuthCredentials *UniversalAuthCredentials `json:"universalAuthCredentials,omitempty"`
+}
+
+type MachineIdentityScopeInWorkspace struct {
+	// +kubebuilder:default="/"
+	// +optional
+	SecretsPath string `json:"secretsPath,omitempty"`
+	// +kubebuilder:validation:Required
+	EnvironmentSlug string `json:"environmentSlug"`
+	// +kubebuilder:validation:Required
+	ProjectSlug string `json:"projectSlug"`
+}
+
+// InfisicalProvider configures a store to sync secrets using the Infisical provider.
+type InfisicalProvider struct {
+	// Auth configures how the Operator authenticates with the Infisical API
+	// +kubebuilder:validation:Required
+	Auth InfisicalAuth `json:"auth"`
+	// +kubebuilder:validation:Required
+	SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
+	// +kubebuilder:default="https://app.infisical.com/api"
+	// +optional
+	HostAPI string `json:"hostAPI,omitempty"`
+}

+ 4 - 0
apis/externalsecrets/v1beta1/secretstore_types.go

@@ -163,6 +163,10 @@ type SecretStoreProvider struct {
 
 
 	// +optional
 	// +optional
 	Passbolt *PassboltProvider `json:"passbolt,omitempty"`
 	Passbolt *PassboltProvider `json:"passbolt,omitempty"`
+
+	// Infisical configures this store to sync secrets using the Infisical provider
+	// +optional
+	Infisical *InfisicalProvider `json:"infisical,omitempty"`
 }
 }
 
 
 type CAProviderType string
 type CAProviderType string

+ 74 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -1670,6 +1670,43 @@ func (in *IBMProvider) DeepCopy() *IBMProvider {
 }
 }
 
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *InfisicalAuth) DeepCopyInto(out *InfisicalAuth) {
+	*out = *in
+	if in.UniversalAuthCredentials != nil {
+		in, out := &in.UniversalAuthCredentials, &out.UniversalAuthCredentials
+		*out = new(UniversalAuthCredentials)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalAuth.
+func (in *InfisicalAuth) DeepCopy() *InfisicalAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(InfisicalAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *InfisicalProvider) DeepCopyInto(out *InfisicalProvider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+	out.SecretsScope = in.SecretsScope
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalProvider.
+func (in *InfisicalProvider) DeepCopy() *InfisicalProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(InfisicalProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *KeeperSecurityProvider) DeepCopyInto(out *KeeperSecurityProvider) {
 func (in *KeeperSecurityProvider) DeepCopyInto(out *KeeperSecurityProvider) {
 	*out = *in
 	*out = *in
 	in.Auth.DeepCopyInto(&out.Auth)
 	in.Auth.DeepCopyInto(&out.Auth)
@@ -1758,6 +1795,21 @@ func (in *KubernetesServer) DeepCopy() *KubernetesServer {
 }
 }
 
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MachineIdentityScopeInWorkspace) DeepCopyInto(out *MachineIdentityScopeInWorkspace) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineIdentityScopeInWorkspace.
+func (in *MachineIdentityScopeInWorkspace) DeepCopy() *MachineIdentityScopeInWorkspace {
+	if in == nil {
+		return nil
+	}
+	out := new(MachineIdentityScopeInWorkspace)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *NoSecretError) DeepCopyInto(out *NoSecretError) {
 func (in *NoSecretError) DeepCopyInto(out *NoSecretError) {
 	*out = *in
 	*out = *in
 }
 }
@@ -2305,6 +2357,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(PassboltProvider)
 		*out = new(PassboltProvider)
 		(*in).DeepCopyInto(*out)
 		(*in).DeepCopyInto(*out)
 	}
 	}
+	if in.Infisical != nil {
+		in, out := &in.Infisical, &out.Infisical
+		*out = new(InfisicalProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 }
 
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
@@ -2617,6 +2674,23 @@ func (in *TokenAuth) DeepCopy() *TokenAuth {
 }
 }
 
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UniversalAuthCredentials) DeepCopyInto(out *UniversalAuthCredentials) {
+	*out = *in
+	in.ClientID.DeepCopyInto(&out.ClientID)
+	in.ClientSecret.DeepCopyInto(&out.ClientSecret)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UniversalAuthCredentials.
+func (in *UniversalAuthCredentials) DeepCopy() *UniversalAuthCredentials {
+	if in == nil {
+		return nil
+	}
+	out := new(UniversalAuthCredentials)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) {
 func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) {
 	*out = *in
 	*out = *in
 	if in.RoleRef != nil {
 	if in.RoleRef != nil {

+ 75 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2883,6 +2883,81 @@ spec:
                     required:
                     required:
                     - auth
                     - auth
                     type: object
                     type: object
+                  infisical:
+                    description: Infisical configures this store to sync secrets using
+                      the Infisical provider
+                    properties:
+                      auth:
+                        description: Auth configures how the Operator authenticates
+                          with the Infisical API
+                        properties:
+                          universalAuthCredentials:
+                            properties:
+                              clientId:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource,
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              clientSecret:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource,
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                            required:
+                            - clientId
+                            - clientSecret
+                            type: object
+                        type: object
+                      hostAPI:
+                        default: https://app.infisical.com/api
+                        type: string
+                      secretsScope:
+                        properties:
+                          environmentSlug:
+                            type: string
+                          projectSlug:
+                            type: string
+                          secretsPath:
+                            default: /
+                            type: string
+                        required:
+                        - environmentSlug
+                        - projectSlug
+                        type: object
+                    required:
+                    - auth
+                    - secretsScope
+                    type: object
                   keepersecurity:
                   keepersecurity:
                     description: KeeperSecurity configures this store to sync secrets
                     description: KeeperSecurity configures this store to sync secrets
                       using the KeeperSecurity provider
                       using the KeeperSecurity provider

+ 75 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2883,6 +2883,81 @@ spec:
                     required:
                     required:
                     - auth
                     - auth
                     type: object
                     type: object
+                  infisical:
+                    description: Infisical configures this store to sync secrets using
+                      the Infisical provider
+                    properties:
+                      auth:
+                        description: Auth configures how the Operator authenticates
+                          with the Infisical API
+                        properties:
+                          universalAuthCredentials:
+                            properties:
+                              clientId:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource,
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              clientSecret:
+                                description: |-
+                                  A reference to a specific 'key' within a Secret resource,
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                            required:
+                            - clientId
+                            - clientSecret
+                            type: object
+                        type: object
+                      hostAPI:
+                        default: https://app.infisical.com/api
+                        type: string
+                      secretsScope:
+                        properties:
+                          environmentSlug:
+                            type: string
+                          projectSlug:
+                            type: string
+                          secretsPath:
+                            default: /
+                            type: string
+                        required:
+                        - environmentSlug
+                        - projectSlug
+                        type: object
+                    required:
+                    - auth
+                    - secretsScope
+                    type: object
                   keepersecurity:
                   keepersecurity:
                     description: KeeperSecurity configures this store to sync secrets
                     description: KeeperSecurity configures this store to sync secrets
                       using the KeeperSecurity provider
                       using the KeeperSecurity provider

+ 142 - 0
deploy/crds/bundle.yaml

@@ -3337,6 +3337,77 @@ spec:
                       required:
                       required:
                         - auth
                         - auth
                       type: object
                       type: object
+                    infisical:
+                      description: Infisical configures this store to sync secrets using the Infisical provider
+                      properties:
+                        auth:
+                          description: Auth configures how the Operator authenticates with the Infisical API
+                          properties:
+                            universalAuthCredentials:
+                              properties:
+                                clientId:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource,
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                clientSecret:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource,
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                              required:
+                                - clientId
+                                - clientSecret
+                              type: object
+                          type: object
+                        hostAPI:
+                          default: https://app.infisical.com/api
+                          type: string
+                        secretsScope:
+                          properties:
+                            environmentSlug:
+                              type: string
+                            projectSlug:
+                              type: string
+                            secretsPath:
+                              default: /
+                              type: string
+                          required:
+                            - environmentSlug
+                            - projectSlug
+                          type: object
+                      required:
+                        - auth
+                        - secretsScope
+                      type: object
                     keepersecurity:
                     keepersecurity:
                       description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
                       description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
                       properties:
                       properties:
@@ -8712,6 +8783,77 @@ spec:
                       required:
                       required:
                         - auth
                         - auth
                       type: object
                       type: object
+                    infisical:
+                      description: Infisical configures this store to sync secrets using the Infisical provider
+                      properties:
+                        auth:
+                          description: Auth configures how the Operator authenticates with the Infisical API
+                          properties:
+                            universalAuthCredentials:
+                              properties:
+                                clientId:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource,
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                clientSecret:
+                                  description: |-
+                                    A reference to a specific 'key' within a Secret resource,
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                              required:
+                                - clientId
+                                - clientSecret
+                              type: object
+                          type: object
+                        hostAPI:
+                          default: https://app.infisical.com/api
+                          type: string
+                        secretsScope:
+                          properties:
+                            environmentSlug:
+                              type: string
+                            projectSlug:
+                              type: string
+                            secretsPath:
+                              default: /
+                              type: string
+                          required:
+                            - environmentSlug
+                            - projectSlug
+                          type: object
+                      required:
+                        - auth
+                        - secretsScope
+                      type: object
                     keepersecurity:
                     keepersecurity:
                       description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
                       description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
                       properties:
                       properties:

+ 191 - 0
docs/api/spec.md

@@ -4372,6 +4372,92 @@ string
 </tr>
 </tr>
 </tbody>
 </tbody>
 </table>
 </table>
+<h3 id="external-secrets.io/v1beta1.InfisicalAuth">InfisicalAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.InfisicalProvider">InfisicalProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>universalAuthCredentials</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.UniversalAuthCredentials">
+UniversalAuthCredentials
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.InfisicalProvider">InfisicalProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>InfisicalProvider configures a store to sync secrets using the Infisical provider.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.InfisicalAuth">
+InfisicalAuth
+</a>
+</em>
+</td>
+<td>
+<p>Auth configures how the Operator authenticates with the Infisical API</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretsScope</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.MachineIdentityScopeInWorkspace">
+MachineIdentityScopeInWorkspace
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>hostAPI</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.KeeperSecurityProvider">KeeperSecurityProvider
 <h3 id="external-secrets.io/v1beta1.KeeperSecurityProvider">KeeperSecurityProvider
 </h3>
 </h3>
 <p>
 <p>
@@ -4586,6 +4672,55 @@ CAProvider
 </tr>
 </tr>
 </tbody>
 </tbody>
 </table>
 </table>
+<h3 id="external-secrets.io/v1beta1.MachineIdentityScopeInWorkspace">MachineIdentityScopeInWorkspace
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.InfisicalProvider">InfisicalProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>secretsPath</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+<tr>
+<td>
+<code>environmentSlug</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>projectSlug</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.NoSecretError">NoSecretError
 <h3 id="external-secrets.io/v1beta1.NoSecretError">NoSecretError
 </h3>
 </h3>
 <p>
 <p>
@@ -6055,6 +6190,20 @@ PassboltProvider
 <em>(Optional)</em>
 <em>(Optional)</em>
 </td>
 </td>
 </tr>
 </tr>
+<tr>
+<td>
+<code>infisical</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.InfisicalProvider">
+InfisicalProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Infisical configures this store to sync secrets using the Infisical provider</p>
+</td>
+</tr>
 </tbody>
 </tbody>
 </table>
 </table>
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
@@ -6932,6 +7081,48 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tr>
 </tbody>
 </tbody>
 </table>
 </table>
+<h3 id="external-secrets.io/v1beta1.UniversalAuthCredentials">UniversalAuthCredentials
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.InfisicalAuth">InfisicalAuth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>clientId</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>clientSecret</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.ValidationResult">ValidationResult
 <h3 id="external-secrets.io/v1beta1.ValidationResult">ValidationResult
 (<code>byte</code> alias)</p></h3>
 (<code>byte</code> alias)</p></h3>
 <p>
 <p>

+ 2 - 0
docs/introduction/stability-support.md

@@ -55,6 +55,7 @@ The following table describes the stability level of each provider and who's res
 | [Delinea](https://external-secrets.io/latest/provider/delinea)                                             |   alpha   |                                                                                                                                     [@michaelsauter](https://github.com/michaelsauter/) |
 | [Delinea](https://external-secrets.io/latest/provider/delinea)                                             |   alpha   |                                                                                                                                     [@michaelsauter](https://github.com/michaelsauter/) |
 | [Pulumi ESC](https://external-secrets.io/latest/provider/pulumi)                                           |   alpha   |                                                                                                                                                  [@dirien](https://github.com/dirien) |
 | [Pulumi ESC](https://external-secrets.io/latest/provider/pulumi)                                           |   alpha   |                                                                                                                                                  [@dirien](https://github.com/dirien) |
 | [Passbolt](https://external-secrets.io/latest/provider/passbolt)                                           |   alpha   |                                                                                                                                                   |
 | [Passbolt](https://external-secrets.io/latest/provider/passbolt)                                           |   alpha   |                                                                                                                                                   |
+| [Infisical](https://external-secrets.io/latest/provider/infisical)                                         |   alpha   | [@akhilmhdh](https://github.com/akhilmhdh)                                                                                       |
 
 
 ## Provider Feature Support
 ## Provider Feature Support
 
 
@@ -84,6 +85,7 @@ The following table show the support for features across different providers.
 | Delinea                   |      x       |              |                      |                         |        x         |             |                             |
 | Delinea                   |      x       |              |                      |                         |        x         |             |                             |
 | Pulumi ESC                |      x       |              |                      |                         |        x         |             |                             |
 | Pulumi ESC                |      x       |              |                      |                         |        x         |             |                             |
 | Passbolt                  |      x       |              |                      |                         |        x         |             |                             |
 | Passbolt                  |      x       |              |                      |                         |        x         |             |                             |
+| Infisical                 |      x       |              |                      |            x            |        x         |             |                             |
 
 
 ## Support Policy
 ## Support Policy
 
 

BIN
docs/pictures/external-secrets-operator.png


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
docs/provider/hashicorp-vault.md


+ 68 - 0
docs/provider/infisical.md

@@ -0,0 +1,68 @@
+![Infisical k8s Diagram](../pictures/external-secrets-operator.png)
+
+Sync secrets from [Infisical](https://www.infisical.com) to your Kubernetes cluster using External Secrets Operator.
+
+## Authentication
+In order for the operator to fetch secrets from Infisical, it needs to first authenticate with Infisical.
+
+To authenticate, you can use [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) from [Machine identities](https://infisical.com/docs/documentation/platform/identities/machine-identities).
+
+Follow the [guide here](https://infisical.com/docs/documentation/platform/identities/universal-auth) to learn how to create and obtain a pair of Client Secret and Client ID.
+
+## Storing Your Machine Identity Secrets
+
+Once you have generated a pair of `Client ID` and `Client Secret`, you will need to store these credentials in your cluster as a Kubernetes secret.
+
+!!! note inline end
+    Remember to replace with your own Machine Identity credentials.
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: universal-auth-credentials
+type: Opaque
+
+stringData:
+  clientId: <machine identity client id>
+  clientSecret: <machine identity client secret>
+```
+
+### Secret Store
+
+You will then need to create a generic `SecretStore`. An sample `SecretStore` has been is shown below.
+
+!!! tip inline end
+    To get your project slug from Infisical, head over to the project settings and click the button `Copy Project Slug`.
+
+```yaml
+{% include 'infisical-generic-secret-store.yaml' %}
+```
+
+!!! Note
+    For `ClusterSecretStore`, be sure to set `namespace` in `universalAuthCredentials.clientId` and `universalAuthCredentials.clientSecret`.
+
+## Fetch Individual Secret(s)
+
+To sync one or more secrets individually, use the following YAML:
+
+```yaml
+{% include 'infisical-fetch-secret.yaml' %}
+```
+
+## Fetch All Secrets
+
+To sync all secrets from an Infisical , use the following YAML:
+
+``` yaml
+{% include 'infisical-fetch-all-secrets.yaml' %}
+```
+
+## Filter By Prefix/Name
+
+To filter secrets by `path` (path prefix) and `name` (regular expression).
+
+``` yaml
+{% include 'infisical-filtered-secrets.yaml' %}
+```
+

+ 16 - 0
docs/snippets/infisical-fetch-all-secrets.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: infisical-managed-secrets
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: infisical
+
+  target:
+    name: auth-api
+
+  dataFrom:
+    - find:
+        name:
+          regexp: .*

+ 16 - 0
docs/snippets/infisical-fetch-secret.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: infisical-managed-secrets
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: infisical
+
+  target:
+    name: auth-api
+
+  data:
+    - secretKey: API_KEY
+      remoteRef:
+        key: API_KEY

+ 15 - 0
docs/snippets/infisical-filtered-secrets.yaml

@@ -0,0 +1,15 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: infisical-managed-secrets
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: infisical
+
+  target:
+    name: auth-api
+
+  dataFrom:
+    - find:
+        path: DB_

+ 25 - 0
docs/snippets/infisical-generic-secret-store.yaml

@@ -0,0 +1,25 @@
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: infisical
+spec:
+  provider:
+    infisical:
+      auth:
+        universalAuthCredentials:
+          clientId:
+            key: clientId
+            namespace: default
+            name: universal-auth-credentials
+          clientSecret:
+            key: clientSecret
+            namespace: default
+            name: universal-auth-credentials
+      # Details to pull secrets from
+      secretsScope:
+        projectSlug: first-project-fujo
+        environmentSlug: dev # "dev", "staging", "prod", etc..
+        # optional
+        secretsPath: / # Root is "/"
+      # optional
+      hostAPI: https://app.infisical.com

+ 72 - 71
hack/api-docs/mkdocs.yml

@@ -39,12 +39,12 @@ extra:
     property: G-QP38TD8K7V
     property: G-QP38TD8K7V
 nav:
 nav:
   - Introduction:
   - Introduction:
-    - Introduction: index.md
-    - Overview: introduction/overview.md
-    - Getting started: introduction/getting-started.md
-    - FAQ: introduction/faq.md
-    - Stability and Support: introduction/stability-support.md
-    - Deprecation Policy: introduction/deprecation-policy.md
+      - Introduction: index.md
+      - Overview: introduction/overview.md
+      - Getting started: introduction/getting-started.md
+      - FAQ: introduction/faq.md
+      - Stability and Support: introduction/stability-support.md
+      - Deprecation Policy: introduction/deprecation-policy.md
   - API:
   - API:
     - Components: api/components.md
     - Components: api/components.md
     - Core Resources:
     - Core Resources:
@@ -68,72 +68,73 @@ nav:
       - Controller Options: api/controller-options.md
       - Controller Options: api/controller-options.md
       - Metrics: api/metrics.md
       - Metrics: api/metrics.md
   - Guides:
   - Guides:
-    - Introduction: guides/introduction.md
-    - External Secrets:
-      - Extract structured data: guides/all-keys-one-secret.md
-      - Find Secrets by Name or Metadata: guides/getallsecrets.md
-      - Rewriting Keys: guides/datafrom-rewrite.md
-      - Advanced Templating:
-        - v2: guides/templating.md
-        - v1: guides/templating-v1.md
-      - Kubernetes Secret Types: guides/common-k8s-secret-types.md
-      - "Lifecycle: ownership & deletion": guides/ownership-deletion-policy.md
-      - Decoding Strategies: guides/decoding-strategy.md
-      - Controller Classes: guides/controller-class.md
-    - Generators: guides/generator.md
-    - Push Secrets: guides/pushsecrets.md
-    - Operations:
-      - Multi Tenancy: guides/multi-tenancy.md
-      - Security Best Practices: guides/security-best-practices.md
-      - Threat Model: guides/threat-model.md
-      - Upgrading to v1beta1: guides/v1beta1.md
-      - Using Latest Image: guides/using-latest-image.md
-      - Disable Cluster Features: guides/disable-cluster-features.md
+      - Introduction: guides/introduction.md
+      - External Secrets:
+          - Extract structured data: guides/all-keys-one-secret.md
+          - Find Secrets by Name or Metadata: guides/getallsecrets.md
+          - Rewriting Keys: guides/datafrom-rewrite.md
+          - Advanced Templating:
+              - v2: guides/templating.md
+              - v1: guides/templating-v1.md
+          - Kubernetes Secret Types: guides/common-k8s-secret-types.md
+          - "Lifecycle: ownership & deletion": guides/ownership-deletion-policy.md
+          - Decoding Strategies: guides/decoding-strategy.md
+          - Controller Classes: guides/controller-class.md
+      - Generators: guides/generator.md
+      - Push Secrets: guides/pushsecrets.md
+      - Operations:
+          - Multi Tenancy: guides/multi-tenancy.md
+          - Security Best Practices: guides/security-best-practices.md
+          - Threat Model: guides/threat-model.md
+          - Upgrading to v1beta1: guides/v1beta1.md
+          - Using Latest Image: guides/using-latest-image.md
+          - Disable Cluster Features: guides/disable-cluster-features.md
   - Provider:
   - Provider:
-    - AWS Secrets Manager: provider/aws-secrets-manager.md
-    - AWS Parameter Store: provider/aws-parameter-store.md
-    - Azure Key Vault: provider/azure-key-vault.md
-    - Chef: provider/chef.md
-    - CyberArk Conjur: provider/conjur.md
-    - Google Cloud Secret Manager: provider/google-secrets-manager.md
-    - HashiCorp Vault: provider/hashicorp-vault.md
-    - Kubernetes: provider/kubernetes.md
-    - IBM Secrets Manager: provider/ibm-secrets-manager.md
-    - Akeyless: provider/akeyless.md
-    - Yandex Certificate Manager: provider/yandex-certificate-manager.md
-    - Yandex Lockbox: provider/yandex-lockbox.md
-    - Alibaba Cloud: provider/alibaba.md
-    - GitLab Variables: provider/gitlab-variables.md
-    - Oracle Vault: provider/oracle-vault.md
-    - 1Password Secrets Automation: provider/1password-automation.md
-    - Webhook: provider/webhook.md
-    - Fake: provider/fake.md
-    - senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
-    - Doppler: provider/doppler.md
-    - Keeper Security: provider/keeper-security.md
-    - Cloak End 2 End Encrypted Secrets: provider/cloak.md
-    - Scaleway: provider/scaleway.md
-    - Delinea: provider/delinea.md
-    - Passbolt: provider/passbolt.md
-    - Pulumi ESC: provider/pulumi.md
-    - Onboardbase: provider/onboardbase.md
-    - Password Depot: provider-passworddepot.md
-    - Fortanix: provider/fortanix.md
+      - AWS Secrets Manager: provider/aws-secrets-manager.md
+      - AWS Parameter Store: provider/aws-parameter-store.md
+      - Azure Key Vault: provider/azure-key-vault.md
+      - Chef: provider/chef.md
+      - CyberArk Conjur: provider/conjur.md
+      - Google Cloud Secret Manager: provider/google-secrets-manager.md
+      - HashiCorp Vault: provider/hashicorp-vault.md
+      - Kubernetes: provider/kubernetes.md
+      - IBM Secrets Manager: provider/ibm-secrets-manager.md
+      - Akeyless: provider/akeyless.md
+      - Yandex Certificate Manager: provider/yandex-certificate-manager.md
+      - Yandex Lockbox: provider/yandex-lockbox.md
+      - Alibaba Cloud: provider/alibaba.md
+      - GitLab Variables: provider/gitlab-variables.md
+      - Oracle Vault: provider/oracle-vault.md
+      - 1Password Secrets Automation: provider/1password-automation.md
+      - Webhook: provider/webhook.md
+      - Fake: provider/fake.md
+      - senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
+      - Doppler: provider/doppler.md
+      - Keeper Security: provider/keeper-security.md
+      - Cloak End 2 End Encrypted Secrets: provider/cloak.md
+      - Scaleway: provider/scaleway.md
+      - Delinea: provider/delinea.md
+      - Passbolt: provider/passbolt.md
+      - Pulumi ESC: provider/pulumi.md
+      - Onboardbase: provider/onboardbase.md
+      - Password Depot: provider-passworddepot.md
+      - Fortanix: provider/fortanix.md
+      - Infisical: provider/infisical.md
   - Examples:
   - Examples:
-    - FluxCD: examples/gitops-using-fluxcd.md
-    - Anchore Engine: examples/anchore-engine-credentials.md
-    - Jenkins: examples/jenkins-kubernetes-credentials.md
-    - BitWarden: examples/bitwarden.md
+      - FluxCD: examples/gitops-using-fluxcd.md
+      - Anchore Engine: examples/anchore-engine-credentials.md
+      - Jenkins: examples/jenkins-kubernetes-credentials.md
+      - BitWarden: examples/bitwarden.md
   - Community:
   - Community:
-    - Contributing:
-      - Developer guide: contributing/devguide.md
-      - Contributing Process: contributing/process.md
-      - Release Process: contributing/release.md
-      - Code of Conduct: contributing/coc.md
-      - Roadmap: contributing/roadmap.md
-    - External Resources:
-      - Talks: eso-talks.md
-      - Demos: eso-demos.md
-      - Blogs: eso-blogs.md
+      - Contributing:
+          - Developer guide: contributing/devguide.md
+          - Contributing Process: contributing/process.md
+          - Release Process: contributing/release.md
+          - Code of Conduct: contributing/coc.md
+          - Roadmap: contributing/roadmap.md
+      - External Resources:
+          - Talks: eso-talks.md
+          - Demos: eso-demos.md
+          - Blogs: eso-blogs.md
   - References:
   - References:
-    - API specification: spec.md
+      - API specification: spec.md

+ 257 - 0
pkg/provider/infisical/api/api.go

@@ -0,0 +1,257 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"time"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/metrics"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/constants"
+)
+
+type InfisicalClient struct {
+	BaseURL *url.URL
+	client  *http.Client
+	token   string
+}
+
+type InfisicalApis interface {
+	MachineIdentityLoginViaUniversalAuth(data MachineIdentityUniversalAuthLoginRequest) (*MachineIdentityDetailsResponse, error)
+	GetSecretsV3(data GetSecretsV3Request) (map[string]string, error)
+	GetSecretByKeyV3(data GetSecretByKeyV3Request) (string, error)
+	RevokeAccessToken() error
+}
+
+const UserAgentName = "k8-external-secrets-operator"
+const errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
+
+func NewAPIClient(baseURL string) (*InfisicalClient, error) {
+	baseParsedURL, err := url.Parse(baseURL)
+	if err != nil {
+		return nil, err
+	}
+
+	api := &InfisicalClient{
+		BaseURL: baseParsedURL,
+		client: &http.Client{
+			Timeout: time.Second * 15,
+		},
+	}
+
+	return api, nil
+}
+
+func (a *InfisicalClient) SetTokenViaMachineIdentity(clientID, clientSecret string) error {
+	if a.token != "" {
+		return nil
+	}
+
+	loginResponse, err := a.MachineIdentityLoginViaUniversalAuth(MachineIdentityUniversalAuthLoginRequest{
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+	})
+	if err != nil {
+		return err
+	}
+
+	a.token = loginResponse.AccessToken
+	return nil
+}
+
+func (a *InfisicalClient) RevokeAccessToken() error {
+	if a.token == "" {
+		return nil
+	}
+	if _, err := a.RevokeMachineIdentityAccessToken(RevokeMachineIdentityAccessTokenRequest{AccessToken: a.token}); err != nil {
+		return err
+	}
+
+	a.token = ""
+	return nil
+}
+
+func (a *InfisicalClient) resolveEndpoint(path string) string {
+	return a.BaseURL.ResolveReference(&url.URL{Path: path}).String()
+}
+
+func (a *InfisicalClient) do(r *http.Request) (*http.Response, error) {
+	if a.token != "" {
+		r.Header.Add("Authorization", "Bearer "+a.token)
+	}
+	r.Header.Add("User-Agent", UserAgentName)
+	r.Header.Add("Content-Type", "application/json")
+
+	return a.client.Do(r)
+}
+
+func (a *InfisicalClient) MachineIdentityLoginViaUniversalAuth(data MachineIdentityUniversalAuthLoginRequest) (*MachineIdentityDetailsResponse, error) {
+	endpointURL := a.resolveEndpoint("api/v1/auth/universal-auth/login")
+	body, err := MarshalReqBody(data)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(http.MethodPost, endpointURL, body)
+	metrics.ObserveAPICall(constants.ProviderName, "MachineIdentityLoginViaUniversalAuth", err)
+	if err != nil {
+		return nil, err
+	}
+
+	rawRes, err := a.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	var res MachineIdentityDetailsResponse
+	err = ReadAndUnmarshal(rawRes, &res)
+	if err != nil {
+		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+	return &res, nil
+}
+
+func (a *InfisicalClient) RevokeMachineIdentityAccessToken(data RevokeMachineIdentityAccessTokenRequest) (*RevokeMachineIdentityAccessTokenResponse, error) {
+	endpointURL := a.resolveEndpoint("api/v1/auth/token/revoke")
+	body, err := MarshalReqBody(data)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(http.MethodPost, endpointURL, body)
+	metrics.ObserveAPICall(constants.ProviderName, "RevokeMachineIdentityAccessToken", err)
+	if err != nil {
+		return nil, err
+	}
+
+	rawRes, err := a.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	var res RevokeMachineIdentityAccessTokenResponse
+	err = ReadAndUnmarshal(rawRes, &res)
+	if err != nil {
+		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+	return &res, nil
+}
+
+func (a *InfisicalClient) GetSecretsV3(data GetSecretsV3Request) (map[string]string, error) {
+	endpointURL := a.resolveEndpoint("api/v3/secrets/raw")
+
+	req, err := http.NewRequest(http.MethodGet, endpointURL, http.NoBody)
+	metrics.ObserveAPICall(constants.ProviderName, "GetSecretsV3", err)
+	if err != nil {
+		return nil, err
+	}
+
+	q := req.URL.Query()
+	q.Add("workspaceSlug", data.ProjectSlug)
+	q.Add("environment", data.EnvironmentSlug)
+	q.Add("secretPath", data.SecretPath)
+	q.Add("include_imports", "true")
+	q.Add("expandSecretReferences", "true")
+	req.URL.RawQuery = q.Encode()
+
+	rawRes, err := a.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	var res GetSecretsV3Response
+	err = ReadAndUnmarshal(rawRes, &res)
+	if err != nil {
+		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+
+	secrets := make(map[string]string)
+	for _, s := range res.ImportedSecrets {
+		for _, el := range s.Secrets {
+			secrets[el.SecretKey] = el.SecretValue
+		}
+	}
+	for _, el := range res.Secrets {
+		secrets[el.SecretKey] = el.SecretValue
+	}
+
+	return secrets, nil
+}
+
+func (a *InfisicalClient) GetSecretByKeyV3(data GetSecretByKeyV3Request) (string, error) {
+	endpointURL := a.resolveEndpoint(fmt.Sprintf("api/v3/secrets/raw/%s", data.SecretKey))
+
+	req, err := http.NewRequest(http.MethodGet, endpointURL, http.NoBody)
+	metrics.ObserveAPICall(constants.ProviderName, "GetSecretByKeyV3", err)
+	if err != nil {
+		return "", err
+	}
+
+	q := req.URL.Query()
+	q.Add("workspaceSlug", data.ProjectSlug)
+	q.Add("environment", data.EnvironmentSlug)
+	q.Add("secretPath", data.SecretPath)
+	q.Add("include_imports", "true")
+	req.URL.RawQuery = q.Encode()
+
+	rawRes, err := a.do(req)
+	if err != nil {
+		return "", err
+	}
+	if rawRes.StatusCode == 400 {
+		var errRes InfisicalAPIErrorResponse
+		err = ReadAndUnmarshal(rawRes, &errRes)
+		if err != nil {
+			return "", fmt.Errorf(errJSONSecretUnmarshal, err)
+		}
+
+		if errRes.Message == "Secret not found" {
+			return "", esv1beta1.NoSecretError{}
+		}
+		return "", errors.New(errRes.Message)
+	}
+
+	var res GetSecretByKeyV3Response
+	err = ReadAndUnmarshal(rawRes, &res)
+	if err != nil {
+		return "", fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+
+	return res.Secret.SecretValue, nil
+}
+
+func MarshalReqBody(data any) (*bytes.Reader, error) {
+	body, err := json.Marshal(data)
+	if err != nil {
+		return nil, err
+	}
+	return bytes.NewReader(body), nil
+}
+
+func ReadAndUnmarshal(resp *http.Response, target any) error {
+	var buf bytes.Buffer
+	defer resp.Body.Close()
+	_, err := buf.ReadFrom(resp.Body)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(buf.Bytes(), target)
+}

+ 87 - 0
pkg/provider/infisical/api/api_models.go

@@ -0,0 +1,87 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+type MachineIdentityUniversalAuthRefreshRequest struct {
+	AccessToken string `json:"accessToken"`
+}
+
+type MachineIdentityDetailsResponse struct {
+	AccessToken       string `json:"accessToken"`
+	ExpiresIn         int    `json:"expiresIn"`
+	AccessTokenMaxTTL int    `json:"accessTokenMaxTTL"`
+	TokenType         string `json:"tokenType"`
+}
+
+type MachineIdentityUniversalAuthLoginRequest struct {
+	ClientID     string `json:"clientId"`
+	ClientSecret string `json:"clientSecret"`
+}
+
+type RevokeMachineIdentityAccessTokenRequest struct {
+	AccessToken string `json:"accessToken"`
+}
+
+type RevokeMachineIdentityAccessTokenResponse struct {
+	Message string `json:"message"`
+}
+
+type GetSecretByKeyV3Request struct {
+	EnvironmentSlug string `json:"environment"`
+	ProjectSlug     string `json:"workspaceSlug"`
+	SecretPath      string `json:"secretPath"`
+	SecretKey       string `json:"secretKey"`
+}
+
+type GetSecretByKeyV3Response struct {
+	Secret SecretsV3 `json:"secret"`
+}
+
+type GetSecretsV3Request struct {
+	EnvironmentSlug string `json:"environment"`
+	ProjectSlug     string `json:"workspaceSlug"`
+	SecretPath      string `json:"secretPath"`
+}
+
+type GetSecretsV3Response struct {
+	Secrets         []SecretsV3        `json:"secrets"`
+	ImportedSecrets []ImportedSecretV3 `json:"imports,omitempty"`
+	Modified        bool               `json:"modified,omitempty"`
+	ETag            string             `json:"ETag,omitempty"`
+}
+
+type SecretsV3 struct {
+	ID            string `json:"id"`
+	Workspace     string `json:"workspace"`
+	Environment   string `json:"environment"`
+	Version       int    `json:"version"`
+	Type          string `json:"string"`
+	SecretKey     string `json:"secretKey"`
+	SecretValue   string `json:"secretValue"`
+	SecretComment string `json:"secretComment"`
+}
+
+type ImportedSecretV3 struct {
+	Environment string      `json:"environment"`
+	FolderID    string      `json:"folderId"`
+	SecretPath  string      `json:"secretPath"`
+	Secrets     []SecretsV3 `json:"secrets"`
+}
+
+type InfisicalAPIErrorResponse struct {
+	StatusCode int    `json:"statusCode"`
+	Message    string `json:"message"`
+	Error      any    `json:"error"`
+}

+ 170 - 0
pkg/provider/infisical/client.go

@@ -0,0 +1,170 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package infisical
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/tidwall/gjson"
+	corev1 "k8s.io/api/core/v1"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/find"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/api"
+)
+
+var (
+	errNotImplemented     = errors.New("not implemented")
+	errPropertyNotFound   = "property %s does not exist in secret %s"
+	errTagsNotImplemented = errors.New("find by tags not supported")
+)
+
+func getPropertyValue(jsonData, propertyName, keyName string) ([]byte, error) {
+	result := gjson.Get(jsonData, propertyName)
+	if !result.Exists() {
+		return nil, fmt.Errorf(errPropertyNotFound, propertyName, keyName)
+	}
+	return []byte(result.Str), nil
+}
+
+// if GetSecret returns an error with type NoSecretError.
+// then the secret entry will be deleted depending on the deletionPolicy.
+func (p *Provider) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	secret, err := p.apiClient.GetSecretByKeyV3(api.GetSecretByKeyV3Request{
+		EnvironmentSlug: p.apiScope.EnvironmentSlug,
+		ProjectSlug:     p.apiScope.ProjectSlug,
+		SecretPath:      p.apiScope.SecretPath,
+		SecretKey:       ref.Key,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	if ref.Property != "" {
+		propertyValue, err := getPropertyValue(secret, ref.Property, ref.Key)
+		if err != nil {
+			return nil, err
+		}
+
+		return propertyValue, nil
+	}
+
+	return []byte(secret), nil
+}
+
+// GetSecretMap returns multiple k/v pairs from the provider.
+func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	secret, err := p.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	kv := make(map[string]json.RawMessage)
+	err = json.Unmarshal(secret, &kv)
+	if err != nil {
+		return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
+	}
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		var strVal string
+		err = json.Unmarshal(v, &strVal)
+		if err == nil {
+			secretData[k] = []byte(strVal)
+		} else {
+			secretData[k] = v
+		}
+	}
+	return secretData, nil
+}
+
+// GetAllSecrets returns multiple k/v pairs from the provider.
+func (p *Provider) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	if ref.Tags != nil {
+		return nil, errTagsNotImplemented
+	}
+
+	secrets, err := p.apiClient.GetSecretsV3(api.GetSecretsV3Request{
+		EnvironmentSlug: p.apiScope.EnvironmentSlug,
+		ProjectSlug:     p.apiScope.ProjectSlug,
+		SecretPath:      p.apiScope.SecretPath,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	secretMap := make(map[string][]byte)
+	for key, value := range secrets {
+		secretMap[key] = []byte(value)
+	}
+	if ref.Name == nil && ref.Path == nil {
+		return secretMap, nil
+	}
+
+	var matcher *find.Matcher
+	if ref.Name != nil {
+		m, err := find.New(*ref.Name)
+		if err != nil {
+			return nil, err
+		}
+		matcher = m
+	}
+
+	selected := map[string][]byte{}
+	for key, value := range secrets {
+		if (matcher != nil && !matcher.MatchName(key)) || (ref.Path != nil && !strings.HasPrefix(key, *ref.Path)) {
+			continue
+		}
+		selected[key] = []byte(value)
+	}
+	return selected, nil
+}
+
+// Validate checks if the client is configured correctly.
+// and is able to retrieve secrets from the provider.
+// If the validation result is unknown it will be ignored.
+func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
+	// try to fetch the secrets to ensure provided credentials has access to read secrets
+	_, err := p.apiClient.GetSecretsV3(api.GetSecretsV3Request{
+		EnvironmentSlug: p.apiScope.EnvironmentSlug,
+		ProjectSlug:     p.apiScope.ProjectSlug,
+		SecretPath:      p.apiScope.SecretPath,
+	})
+
+	if err != nil {
+		return esv1beta1.ValidationResultError, fmt.Errorf("cannot read secrets with provided project scope project:%s environment:%s secret-path:%s, %w", p.apiScope.ProjectSlug, p.apiScope.EnvironmentSlug, p.apiScope.SecretPath, err)
+	}
+
+	return esv1beta1.ValidationResultReady, nil
+}
+
+// PushSecret will write a single secret into the provider.
+func (p *Provider) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
+	return errNotImplemented
+}
+
+// DeleteSecret will delete the secret from a provider.
+func (p *Provider) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) error {
+	return errNotImplemented
+}
+
+// SecretExists checks if a secret is already present in the provider at the given location.
+func (p *Provider) SecretExists(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) (bool, error) {
+	return false, errNotImplemented
+}

+ 19 - 0
pkg/provider/infisical/constants/constants.go

@@ -0,0 +1,19 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package constants
+
+const (
+	UniversalAuth = "universal-auth"
+	ProviderName  = "infisical"
+)

+ 58 - 0
pkg/provider/infisical/fake/fake.go

@@ -0,0 +1,58 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package fake
+
+import (
+	"errors"
+	"time"
+
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/api"
+)
+
+var (
+	ErrMissingMockImplementation = errors.New("missing mock implmentation")
+)
+
+type MockInfisicalClient struct {
+	MockedGetSecretV3      func(data api.GetSecretsV3Request) (map[string]string, error)
+	MockedGetSecretByKeyV3 func(data api.GetSecretByKeyV3Request) (string, error)
+}
+
+func (a *MockInfisicalClient) MachineIdentityLoginViaUniversalAuth(data api.MachineIdentityUniversalAuthLoginRequest) (*api.MachineIdentityDetailsResponse, error) {
+	return &api.MachineIdentityDetailsResponse{
+		AccessToken:       "test-access-token",
+		ExpiresIn:         int(time.Hour * 24),
+		TokenType:         "bearer",
+		AccessTokenMaxTTL: int(time.Hour * 24 * 2),
+	}, nil
+}
+
+func (a *MockInfisicalClient) GetSecretsV3(data api.GetSecretsV3Request) (map[string]string, error) {
+	if a.MockedGetSecretV3 == nil {
+		return nil, ErrMissingMockImplementation
+	}
+
+	return a.MockedGetSecretV3(data)
+}
+
+func (a *MockInfisicalClient) GetSecretByKeyV3(data api.GetSecretByKeyV3Request) (string, error) {
+	if a.MockedGetSecretByKeyV3 == nil {
+		return "", ErrMissingMockImplementation
+	}
+	return a.MockedGetSecretByKeyV3(data)
+}
+
+func (a *MockInfisicalClient) RevokeAccessToken() error {
+	return nil
+}

+ 159 - 0
pkg/provider/infisical/provider.go

@@ -0,0 +1,159 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implieclient.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package infisical
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/api"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/constants"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
+)
+
+var (
+	Logger = ctrl.Log.WithName("provider").WithName(constants.ProviderName)
+)
+
+type Provider struct {
+	apiClient api.InfisicalApis
+	apiScope  *InfisicalClientScope
+}
+
+type InfisicalClientScope struct {
+	SecretPath      string
+	ProjectSlug     string
+	EnvironmentSlug string
+}
+
+// https://github.com/external-secrets/external-secrets/issues/644
+var _ esv1beta1.SecretsClient = &Provider{}
+var _ esv1beta1.Provider = &Provider{}
+
+func init() {
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+		Infisical: &esv1beta1.InfisicalProvider{},
+	})
+}
+
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Infisical == nil {
+		return nil, errors.New("invalid infisical store")
+	}
+
+	infisicalSpec := storeSpec.Provider.Infisical
+
+	apiClient, err := api.NewAPIClient(infisicalSpec.HostAPI)
+	if err != nil {
+		return nil, err
+	}
+
+	if infisicalSpec.Auth.UniversalAuthCredentials != nil {
+		universalAuthCredentials := infisicalSpec.Auth.UniversalAuthCredentials
+		clientID, err := GetStoreSecretData(ctx, store, kube, namespace, universalAuthCredentials.ClientID)
+		if err != nil {
+			return nil, err
+		}
+
+		clientSecret, err := GetStoreSecretData(ctx, store, kube, namespace, universalAuthCredentials.ClientSecret)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := apiClient.SetTokenViaMachineIdentity(clientID, clientSecret); err != nil {
+			return nil, fmt.Errorf("failed to authenticate via universal auth %w", err)
+		}
+
+		return &Provider{
+			apiClient: apiClient,
+			apiScope: &InfisicalClientScope{
+				SecretPath:      infisicalSpec.SecretsScope.SecretsPath,
+				ProjectSlug:     infisicalSpec.SecretsScope.ProjectSlug,
+				EnvironmentSlug: infisicalSpec.SecretsScope.EnvironmentSlug,
+			},
+		}, nil
+	}
+
+	return &Provider{}, errors.New("authentication method not found")
+}
+
+func (p *Provider) Close(ctx context.Context) error {
+	if err := p.apiClient.RevokeAccessToken(); err != nil {
+		return err
+	}
+	return nil
+}
+
+func GetStoreSecretData(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string, secret esmeta.SecretKeySelector) (string, error) {
+	secretRef := esmeta.SecretKeySelector{
+		Name: secret.Name,
+		Key:  secret.Key,
+	}
+	if secret.Namespace != nil {
+		secretRef.Namespace = secret.Namespace
+	}
+
+	secretData, err := resolvers.SecretKeyRef(ctx, kube, store.GetObjectKind().GroupVersionKind().Kind, namespace, &secretRef)
+	if err != nil {
+		return "", err
+	}
+	return secretData, nil
+}
+
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
+	storeSpec := store.GetSpec()
+	infisicalStoreSpec := storeSpec.Provider.Infisical
+	if infisicalStoreSpec == nil {
+		return nil, errors.New("invalid infisical store")
+	}
+
+	if infisicalStoreSpec.SecretsScope.EnvironmentSlug == "" || infisicalStoreSpec.SecretsScope.ProjectSlug == "" {
+		return nil, errors.New("secretsScope.projectSlug and secretsScope.environmentSlug cannot be empty")
+	}
+
+	if infisicalStoreSpec.Auth.UniversalAuthCredentials != nil {
+		uaCredential := infisicalStoreSpec.Auth.UniversalAuthCredentials
+		// to validate reference authentication
+		err := utils.ValidateReferentSecretSelector(store, uaCredential.ClientID)
+		if err != nil {
+			return nil, err
+		}
+
+		err = utils.ValidateReferentSecretSelector(store, uaCredential.ClientSecret)
+		if err != nil {
+			return nil, err
+		}
+
+		if uaCredential.ClientID.Key == "" || uaCredential.ClientSecret.Key == "" {
+			return nil, errors.New("universalAuthCredentials.clientId and universalAuthCredentials.clientSecret cannot be empty")
+		}
+	}
+
+	return nil, nil
+}

+ 238 - 0
pkg/provider/infisical/provider_test.go

@@ -0,0 +1,238 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliec.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package infisical
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/api"
+	"github.com/external-secrets/external-secrets/pkg/provider/infisical/fake"
+)
+
+type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
+
+var apiScope = InfisicalClientScope{
+	SecretPath:      "/",
+	ProjectSlug:     "first-project",
+	EnvironmentSlug: "dev",
+}
+
+type TestCases struct {
+	Name           string
+	MockClient     *fake.MockInfisicalClient
+	PropertyAccess string
+	Error          error
+	Output         any
+}
+
+func TestGetSecret(t *testing.T) {
+	testCases := []TestCases{
+		{
+			Name: "Get_valid_key",
+			MockClient: &fake.MockInfisicalClient{
+				MockedGetSecretByKeyV3: func(data api.GetSecretByKeyV3Request) (string, error) {
+					return "value", nil
+				},
+			},
+			Error:  nil,
+			Output: []byte("value"),
+		},
+		{
+			Name: "Get_property_key",
+			MockClient: &fake.MockInfisicalClient{
+				MockedGetSecretByKeyV3: func(data api.GetSecretByKeyV3Request) (string, error) {
+					return `{"key":"value"}`, nil
+				},
+			},
+			Error:  nil,
+			Output: []byte("value"),
+		},
+		{
+			Name: "Key_not_found",
+			MockClient: &fake.MockInfisicalClient{
+				MockedGetSecretByKeyV3: func(data api.GetSecretByKeyV3Request) (string, error) {
+					// from server
+					return "", errors.New("Secret not found")
+				},
+			},
+			Error:  errors.New("Secret not found"),
+			Output: "",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			p := &Provider{
+				apiClient: tc.MockClient,
+				apiScope:  &apiScope,
+			}
+			var property string
+			if tc.Name == "Get_property_key" {
+				property = "key"
+			}
+
+			output, err := p.GetSecret(context.Background(), esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "key",
+				Property: property,
+			})
+
+			if tc.Error == nil {
+				assert.NoError(t, err)
+				assert.Equal(t, tc.Output, output)
+			} else {
+				assert.ErrorAs(t, err, &tc.Error)
+			}
+		})
+	}
+}
+
+func TestGetSecretMap(t *testing.T) {
+	testCases := []TestCases{
+		{
+			Name: "Get_valid_key_map",
+			MockClient: &fake.MockInfisicalClient{
+				MockedGetSecretByKeyV3: func(data api.GetSecretByKeyV3Request) (string, error) {
+					return `{"key":"value"}`, nil
+				},
+			},
+			Error: nil,
+			Output: map[string][]byte{
+				"key": []byte("value"),
+			},
+		},
+		{
+			Name: "Get_invalid_map",
+			MockClient: &fake.MockInfisicalClient{
+				MockedGetSecretByKeyV3: func(data api.GetSecretByKeyV3Request) (string, error) {
+					return ``, nil
+				},
+			},
+			Error:  errors.New("unexpected end of JSON input"),
+			Output: nil,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			p := &Provider{
+				apiClient: tc.MockClient,
+				apiScope:  &apiScope,
+			}
+			output, err := p.GetSecretMap(context.Background(), esv1beta1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			})
+			if tc.Error == nil {
+				assert.NoError(t, err)
+				assert.Equal(t, tc.Output, output)
+			} else {
+				assert.ErrorAs(t, err, &tc.Error)
+			}
+		})
+	}
+}
+
+func makeSecretStore(projectSlug, environment, secretPath string, fn ...storeModifier) *esv1beta1.SecretStore {
+	store := &esv1beta1.SecretStore{
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				Infisical: &esv1beta1.InfisicalProvider{
+					Auth: esv1beta1.InfisicalAuth{
+						UniversalAuthCredentials: &esv1beta1.UniversalAuthCredentials{},
+					},
+					SecretsScope: esv1beta1.MachineIdentityScopeInWorkspace{
+						SecretsPath:     secretPath,
+						EnvironmentSlug: environment,
+						ProjectSlug:     projectSlug,
+					},
+				},
+			},
+		},
+	}
+	for _, f := range fn {
+		store = f(store)
+	}
+	return store
+}
+
+func withClientID(name, key string, namespace *string) storeModifier {
+	return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
+		store.Spec.Provider.Infisical.Auth.UniversalAuthCredentials.ClientID = esv1meta.SecretKeySelector{
+			Name:      name,
+			Key:       key,
+			Namespace: namespace,
+		}
+		return store
+	}
+}
+
+func withClientSecret(name, key string, namespace *string) storeModifier {
+	return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
+		store.Spec.Provider.Infisical.Auth.UniversalAuthCredentials.ClientSecret = esv1meta.SecretKeySelector{
+			Name:      name,
+			Key:       key,
+			Namespace: namespace,
+		}
+		return store
+	}
+}
+
+type ValidateStoreTestCase struct {
+	store       *esv1beta1.SecretStore
+	assertError func(t *testing.T, err error)
+}
+
+func TestValidateStore(t *testing.T) {
+	const randomID = "some-random-id"
+	const authType = "universal-auth"
+	var authCredMissingErr = errors.New("universalAuthCredentials.clientId and universalAuthCredentials.clientSecret cannot be empty")
+	var authScopeMissingErr = errors.New("secretsScope.projectSlug and secretsScope.environmentSlug cannot be empty")
+
+	testCases := []ValidateStoreTestCase{
+		{
+			store: makeSecretStore("", "", ""),
+			assertError: func(t *testing.T, err error) {
+				require.ErrorAs(t, err, &authScopeMissingErr)
+			},
+		},
+		{
+			store: makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientID(authType, randomID, nil)),
+			assertError: func(t *testing.T, err error) {
+				require.ErrorAs(t, err, &authCredMissingErr)
+			},
+		},
+		{
+			store: makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientSecret(authType, randomID, nil)),
+			assertError: func(t *testing.T, err error) {
+				require.ErrorAs(t, err, &authCredMissingErr)
+			},
+		},
+		{
+			store:       makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientID(authType, randomID, nil), withClientSecret(authType, randomID, nil)),
+			assertError: func(t *testing.T, err error) { require.NoError(t, err) },
+		},
+	}
+	p := Provider{}
+	for _, tc := range testCases {
+		_, err := p.ValidateStore(tc.store)
+		tc.assertError(t, err)
+	}
+}

+ 1 - 0
pkg/provider/register/register.go

@@ -30,6 +30,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gitlab"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gitlab"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/infisical"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/keepersecurity"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/keepersecurity"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/kubernetes"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/kubernetes"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/onboardbase"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/onboardbase"