Browse Source

feat: add 1Password SDK based provider (#4628)

* feat: add 1Password SDK based provider

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* adding test cases

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* add documentation for the provider

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* finished the documentation and updated samples added support doc and unit tests

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* add PushSecret and SecretExists functionality to the provider

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* changed the default values for the integration

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* add documentation update and increase test coverage

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Gustavo Fernandes de Carvalho <17139678+gusfcarvalho@users.noreply.github.com>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* addressed comments

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* do not implement secret exists

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

* add document about label uniquness

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

---------

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Signed-off-by: Gustavo Fernandes de Carvalho <17139678+gusfcarvalho@users.noreply.github.com>
Co-authored-by: Gustavo Fernandes de Carvalho <17139678+gusfcarvalho@users.noreply.github.com>
Gergely Brautigam 11 months ago
parent
commit
fa1d9bd333

+ 47 - 0
apis/externalsecrets/v1/secretstore_onepassword_sdk_types.go

@@ -0,0 +1,47 @@
+/*
+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 v1
+
+import (
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+// OnePasswordSDKAuth contains a secretRef for the service account token.
+type OnePasswordSDKAuth struct {
+	// ServiceAccountSecretRef points to the secret containing the token to access 1Password vault.
+	ServiceAccountSecretRef esmeta.SecretKeySelector `json:"serviceAccountSecretRef"`
+}
+
+// IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+type IntegrationInfo struct {
+	// Name defaults to "1Password SDK".
+	// +kubebuilder:default="1Password SDK"
+	Name string `json:"name,omitempty"`
+	// Version defaults to "v1.0.0".
+	// +kubebuilder:default="v1.0.0"
+	Version string `json:"version,omitempty"`
+}
+
+// OnePasswordSDKProvider configures a store to sync secrets using the 1Password sdk.
+type OnePasswordSDKProvider struct {
+	// Vault defines the vault's name to access. Do NOT add op:// prefix. This will be done automatically.
+	Vault string `json:"vault"`
+	// IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+	// If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+	// +optional
+	IntegrationInfo *IntegrationInfo `json:"integrationInfo,omitempty"`
+	// Auth defines the information necessary to authenticate against OnePassword API.
+	Auth *OnePasswordSDKAuth `json:"auth"`
+}

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

@@ -121,6 +121,10 @@ type SecretStoreProvider struct {
 	// +optional
 	OnePassword *OnePasswordProvider `json:"onepassword,omitempty"`
 
+	// OnePasswordSDK configures this store to use 1Password's new Go SDK to sync secrets.
+	// +optional
+	OnePasswordSDK *OnePasswordSDKProvider `json:"onepasswordSDK,omitempty"`
+
 	// Webhook configures this store to sync secrets using a generic templated webhook
 	// +optional
 	Webhook *WebhookProvider `json:"webhook,omitempty"`

+ 61 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -2009,6 +2009,21 @@ func (in *InfisicalProvider) DeepCopy() *InfisicalProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *IntegrationInfo) DeepCopyInto(out *IntegrationInfo) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationInfo.
+func (in *IntegrationInfo) DeepCopy() *IntegrationInfo {
+	if in == nil {
+		return nil
+	}
+	out := new(IntegrationInfo)
+	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) {
 	*out = *in
 	in.Auth.DeepCopyInto(&out.Auth)
@@ -2264,6 +2279,47 @@ func (in *OnePasswordProvider) DeepCopy() *OnePasswordProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OnePasswordSDKAuth) DeepCopyInto(out *OnePasswordSDKAuth) {
+	*out = *in
+	in.ServiceAccountSecretRef.DeepCopyInto(&out.ServiceAccountSecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnePasswordSDKAuth.
+func (in *OnePasswordSDKAuth) DeepCopy() *OnePasswordSDKAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(OnePasswordSDKAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OnePasswordSDKProvider) DeepCopyInto(out *OnePasswordSDKProvider) {
+	*out = *in
+	if in.IntegrationInfo != nil {
+		in, out := &in.IntegrationInfo, &out.IntegrationInfo
+		*out = new(IntegrationInfo)
+		**out = **in
+	}
+	if in.Auth != nil {
+		in, out := &in.Auth, &out.Auth
+		*out = new(OnePasswordSDKAuth)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnePasswordSDKProvider.
+func (in *OnePasswordSDKProvider) DeepCopy() *OnePasswordSDKProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(OnePasswordSDKProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *OracleAuth) DeepCopyInto(out *OracleAuth) {
 	*out = *in
 	in.SecretRef.DeepCopyInto(&out.SecretRef)
@@ -2728,6 +2784,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(OnePasswordProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.OnePasswordSDK != nil {
+		in, out := &in.OnePasswordSDK, &out.OnePasswordSDK
+		*out = new(OnePasswordSDKProvider)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Webhook != nil {
 		in, out := &in.Webhook, &out.Webhook
 		*out = new(WebhookProvider)

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

@@ -2648,6 +2648,67 @@ spec:
                     - connectHost
                     - vaults
                     type: object
+                  onepasswordSDK:
+                    description: OnePasswordSDK configures this store to use 1Password's
+                      new Go SDK to sync secrets.
+                    properties:
+                      auth:
+                        description: Auth defines the information necessary to authenticate
+                          against OnePassword API.
+                        properties:
+                          serviceAccountSecretRef:
+                            description: ServiceAccountSecretRef points to the secret
+                              containing the token to access 1Password vault.
+                            properties:
+                              key:
+                                description: |-
+                                  A key in the referenced Secret.
+                                  Some instances of this field may be defaulted, in others it may be required.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[-._a-zA-Z0-9]+$
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                type: string
+                              namespace:
+                                description: |-
+                                  The namespace of the Secret resource being referred to.
+                                  Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                maxLength: 63
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                type: string
+                            type: object
+                        required:
+                        - serviceAccountSecretRef
+                        type: object
+                      integrationInfo:
+                        description: |-
+                          IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+                          If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+                        properties:
+                          name:
+                            default: 1Password SDK
+                            description: Name defaults to "1Password SDK".
+                            type: string
+                          version:
+                            default: v1.0.0
+                            description: Version defaults to "v1.0.0".
+                            type: string
+                        type: object
+                      vault:
+                        description: Vault defines the vault's name to access. Do
+                          NOT add op:// prefix. This will be done automatically.
+                        type: string
+                    required:
+                    - auth
+                    - vault
+                    type: object
                   oracle:
                     description: Oracle configures this store to sync secrets using
                       Oracle Vault provider

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

@@ -2648,6 +2648,67 @@ spec:
                     - connectHost
                     - vaults
                     type: object
+                  onepasswordSDK:
+                    description: OnePasswordSDK configures this store to use 1Password's
+                      new Go SDK to sync secrets.
+                    properties:
+                      auth:
+                        description: Auth defines the information necessary to authenticate
+                          against OnePassword API.
+                        properties:
+                          serviceAccountSecretRef:
+                            description: ServiceAccountSecretRef points to the secret
+                              containing the token to access 1Password vault.
+                            properties:
+                              key:
+                                description: |-
+                                  A key in the referenced Secret.
+                                  Some instances of this field may be defaulted, in others it may be required.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[-._a-zA-Z0-9]+$
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                type: string
+                              namespace:
+                                description: |-
+                                  The namespace of the Secret resource being referred to.
+                                  Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                maxLength: 63
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                type: string
+                            type: object
+                        required:
+                        - serviceAccountSecretRef
+                        type: object
+                      integrationInfo:
+                        description: |-
+                          IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+                          If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+                        properties:
+                          name:
+                            default: 1Password SDK
+                            description: Name defaults to "1Password SDK".
+                            type: string
+                          version:
+                            default: v1.0.0
+                            description: Version defaults to "v1.0.0".
+                            type: string
+                        type: object
+                      vault:
+                        description: Vault defines the vault's name to access. Do
+                          NOT add op:// prefix. This will be done automatically.
+                        type: string
+                    required:
+                    - auth
+                    - vault
+                    type: object
                   oracle:
                     description: Oracle configures this store to sync secrets using
                       Oracle Vault provider

+ 57 - 1
deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap

@@ -4,7 +4,7 @@ should match snapshot of default values:
     kind: CustomResourceDefinition
     metadata:
       annotations:
-        controller-gen.kubebuilder.io/version: v0.17.3
+        controller-gen.kubebuilder.io/version: v0.18.0
       labels:
         external-secrets.io/component: controller
       name: secretstores.external-secrets.io
@@ -2452,6 +2452,62 @@ should match snapshot of default values:
                             - connectHost
                             - vaults
                           type: object
+                        onepasswordSDK:
+                          description: OnePasswordSDK configures this store to use 1Password's new Go SDK to sync secrets.
+                          properties:
+                            auth:
+                              description: Auth defines the information necessary to authenticate against OnePassword API.
+                              properties:
+                                serviceAccountSecretRef:
+                                  description: ServiceAccountSecretRef points to the secret containing the token to access 1Password vault.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - serviceAccountSecretRef
+                              type: object
+                            integrationInfo:
+                              description: |-
+                                IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+                                If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+                              properties:
+                                name:
+                                  default: 1Password SDK
+                                  description: Name defaults to "1Password SDK".
+                                  type: string
+                                version:
+                                  default: v1.0.0
+                                  description: Version defaults to "v1.0.0".
+                                  type: string
+                              type: object
+                            vault:
+                              description: Vault defines the vault's name to access. Do NOT add op:// prefix. This will be done automatically.
+                              type: string
+                          required:
+                            - auth
+                            - vault
+                          type: object
                         oracle:
                           description: Oracle configures this store to sync secrets using Oracle Vault provider
                           properties:

+ 112 - 0
deploy/crds/bundle.yaml

@@ -4423,6 +4423,62 @@ spec:
                         - connectHost
                         - vaults
                       type: object
+                    onepasswordSDK:
+                      description: OnePasswordSDK configures this store to use 1Password's new Go SDK to sync secrets.
+                      properties:
+                        auth:
+                          description: Auth defines the information necessary to authenticate against OnePassword API.
+                          properties:
+                            serviceAccountSecretRef:
+                              description: ServiceAccountSecretRef points to the secret containing the token to access 1Password vault.
+                              properties:
+                                key:
+                                  description: |-
+                                    A key in the referenced Secret.
+                                    Some instances of this field may be defaulted, in others it may be required.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[-._a-zA-Z0-9]+$
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    The namespace of the Secret resource being referred to.
+                                    Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                  maxLength: 63
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                  type: string
+                              type: object
+                          required:
+                            - serviceAccountSecretRef
+                          type: object
+                        integrationInfo:
+                          description: |-
+                            IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+                            If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+                          properties:
+                            name:
+                              default: 1Password SDK
+                              description: Name defaults to "1Password SDK".
+                              type: string
+                            version:
+                              default: v1.0.0
+                              description: Version defaults to "v1.0.0".
+                              type: string
+                          type: object
+                        vault:
+                          description: Vault defines the vault's name to access. Do NOT add op:// prefix. This will be done automatically.
+                          type: string
+                      required:
+                        - auth
+                        - vault
+                      type: object
                     oracle:
                       description: Oracle configures this store to sync secrets using Oracle Vault provider
                       properties:
@@ -14411,6 +14467,62 @@ spec:
                         - connectHost
                         - vaults
                       type: object
+                    onepasswordSDK:
+                      description: OnePasswordSDK configures this store to use 1Password's new Go SDK to sync secrets.
+                      properties:
+                        auth:
+                          description: Auth defines the information necessary to authenticate against OnePassword API.
+                          properties:
+                            serviceAccountSecretRef:
+                              description: ServiceAccountSecretRef points to the secret containing the token to access 1Password vault.
+                              properties:
+                                key:
+                                  description: |-
+                                    A key in the referenced Secret.
+                                    Some instances of this field may be defaulted, in others it may be required.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[-._a-zA-Z0-9]+$
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    The namespace of the Secret resource being referred to.
+                                    Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                  maxLength: 63
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                  type: string
+                              type: object
+                          required:
+                            - serviceAccountSecretRef
+                          type: object
+                        integrationInfo:
+                          description: |-
+                            IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+                            If you don't know which name and version to use, use `DefaultIntegrationName` and `DefaultIntegrationVersion`, respectively.
+                          properties:
+                            name:
+                              default: 1Password SDK
+                              description: Name defaults to "1Password SDK".
+                              type: string
+                            version:
+                              default: v1.0.0
+                              description: Version defaults to "v1.0.0".
+                              type: string
+                          type: object
+                        vault:
+                          description: Vault defines the vault's name to access. Do NOT add op:// prefix. This will be done automatically.
+                          type: string
+                      required:
+                        - auth
+                        - vault
+                      type: object
                     oracle:
                       description: Oracle configures this store to sync secrets using Oracle Vault provider
                       properties:

+ 145 - 0
docs/api/spec.md

@@ -5391,6 +5391,47 @@ string
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.IntegrationInfo">IntegrationInfo
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OnePasswordSDKProvider">OnePasswordSDKProvider</a>)
+</p>
+<p>
+<p>IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>name</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Name defaults to &ldquo;1Password SDK&rdquo;.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>version</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Version defaults to &ldquo;v1.0.0&rdquo;.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.KeeperSecurityProvider">KeeperSecurityProvider
 </h3>
 <p>
@@ -5998,6 +6039,96 @@ map[string]int
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.OnePasswordSDKAuth">OnePasswordSDKAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OnePasswordSDKProvider">OnePasswordSDKProvider</a>)
+</p>
+<p>
+<p>OnePasswordSDKAuth contains a secretRef for the service account token.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>serviceAccountSecretRef</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>
+<p>ServiceAccountSecretRef points to the secret containing the token to access 1Password vault.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.OnePasswordSDKProvider">OnePasswordSDKProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>OnePasswordSDKProvider configures a store to sync secrets using the 1Password sdk.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>vault</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Vault defines the vault&rsquo;s name to access. Do NOT add op:// prefix. This will be done automatically.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>integrationInfo</code></br>
+<em>
+<a href="#external-secrets.io/v1.IntegrationInfo">
+IntegrationInfo
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
+If you don&rsquo;t know which name and version to use, use <code>DefaultIntegrationName</code> and <code>DefaultIntegrationVersion</code>, respectively.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1.OnePasswordSDKAuth">
+OnePasswordSDKAuth
+</a>
+</em>
+</td>
+<td>
+<p>Auth defines the information necessary to authenticate against OnePassword API.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.OracleAuth">OracleAuth
 </h3>
 <p>
@@ -7271,6 +7402,20 @@ OnePasswordProvider
 </tr>
 <tr>
 <td>
+<code>onepasswordSDK</code></br>
+<em>
+<a href="#external-secrets.io/v1.OnePasswordSDKProvider">
+OnePasswordSDKProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>OnePasswordSDK configures this store to use 1Password&rsquo;s new Go SDK to sync secrets.</p>
+</td>
+</tr>
+<tr>
+<td>
 <code>webhook</code></br>
 <em>
 <a href="#external-secrets.io/v1.WebhookProvider">

+ 3 - 1
docs/introduction/stability-support.md

@@ -52,6 +52,7 @@ The following table describes the stability level of each provider and who's res
 | [Oracle Vault](https://external-secrets.io/latest/provider/oracle-vault)                                   | alpha     | **UNMAINTAINED**                                                                                    |
 | [Akeyless](https://external-secrets.io/latest/provider/akeyless)                                           | stable    | [external-secrets](https://github.com/external-secrets)                                             |
 | [1Password](https://external-secrets.io/latest/provider/1password-automation)                              | alpha     | [@SimSpaceCorp](https://github.com/Simspace) [@snarlysodboxer](https://github.com/snarlysodboxer)   |
+| [1Password SDK](https://external-secrets.io/latest/provider/1password-sdk)                                 | alpha     | [@Skarlso](https://github.com/Skarlso)                                                              |
 | [Generic Webhook](https://external-secrets.io/latest/provider/webhook)                                     | alpha     | [@willemm](https://github.com/willemm)                                                              |
 | [senhasegura DevOps Secrets Management (DSM)](https://external-secrets.io/latest/provider/senhasegura-dsm) | alpha     | [@lfraga](https://github.com/lfraga)                                                                |
 | [Doppler SecretOps Platform](https://external-secrets.io/latest/provider/doppler)                          | alpha     | [@ryan-blunden](https://github.com/ryan-blunden/) [@nmanoogian](https://github.com/nmanoogian/)     |
@@ -75,7 +76,7 @@ The following table describes the stability level of each provider and who's res
 The following table show the support for features across different providers.
 
 | Provider                  | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret | DeletionPolicy Merge/Delete |
-| ------------------------- | :----------: | :----------: | :------------------: | :---------------------: | :--------------: | :---------: | :-------------------------: |
+|---------------------------|:------------:|:------------:|:--------------------:|:-----------------------:|:----------------:|:-----------:|:---------------------------:|
 | AWS Secrets Manager       |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
 | AWS Parameter Store       |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
 | Hashicorp Vault           |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
@@ -89,6 +90,7 @@ The following table show the support for features across different providers.
 | Oracle Vault              |              |              |                      |                         |        x         |             |                             |
 | Akeyless                  |      x       |      x       |                      |            x            |        x         |      x      |              x              |
 | 1Password                 |      x       |      x       |                      |                         |        x         |      x      |              x              |
+| 1Password SDK             |              |              |                      |                         |        x         |      x      |              x              |
 | Generic Webhook           |              |              |                      |                         |                  |             |              x              |
 | senhasegura DSM           |              |              |                      |                         |        x         |             |                             |
 | Doppler                   |      x       |              |                      |                         |        x         |             |                             |

+ 5 - 0
docs/provider/1password-automation.md

@@ -2,6 +2,11 @@
 
 External Secrets Operator integrates with [1Password Secrets Automation](https://1password.com/products/secrets/) for secret management.
 
+## Deprecation
+
+Consider using [1Password SDK provider](1password-sdk.md) instead. It uses an official [SDK for 1Password](https://developer.1password.com/docs/sdks) created
+by 1Password. It's feature complete and has parity with this provider's capabilities.
+
 ### Important note about this documentation
 
 _**The 1Password API calls the entries in vaults 'Items'. These docs use the same term.**_

+ 57 - 0
docs/provider/1password-sdk.md

@@ -0,0 +1,57 @@
+## 1Password Secrets with SDK
+
+1Password released [developer SDKs](https://developer.1password.com/docs/sdks/) to ease the usage of the secret provider
+without the need for any external devices. This provides a much better user experience for automated processes without
+the need of the connect server.
+
+_Note_: In order to use ESO with 1Password SDK, documents must have unique label names. Meaning, if there is a label
+that has the same title as another label we won't know which one to update and an error is thrown:
+`found multiple labels with the same key`.
+
+### Store Configuration
+
+A store is per vault. This is to prevent a single ExternalSecret potentially accessing ALL vaults.
+
+A sample store configuration looks like this:
+
+```yaml
+{% include '1passwordsdk-secret-store.yaml' %}
+```
+
+### GetSecret
+
+Valid secret references should use the following key format: `<item>/[section/]<field>`.
+
+This is described here: [Secret Reference Syntax](https://developer.1password.com/docs/cli/secret-reference-syntax/).
+
+For a one-time password use the following key format: `<item>/[section/]one-time password?attribute=otp`.
+
+```yaml
+{% include '1passwordsdk-external-secret.yaml' %}
+```
+
+### PushSecret
+
+Pushing a secret is also supported. For example a push operation with the following secret:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: source-secret
+stringData:
+  source-key: "my-secret"
+```
+
+Looks like this:
+
+```yaml
+{% include '1passwordsdk-push-secret.yaml' %}
+```
+
+Once all fields of a secret are deleted, the entire secret is deleted if the PushSecret object is removed and
+policy is set to `delete`.
+
+### Supported Functionality
+
+Please check the documentation on 1password for [Supported Functionality](https://developer.1password.com/docs/sdks/functionality).

+ 15 - 0
docs/snippets/1passwordsdk-external-secret.yaml

@@ -0,0 +1,15 @@
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: fetch-from-onepassword
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: onepassword
+  target:
+    creationPolicy: Owner
+  data:
+    - secretKey: test-login-1
+      remoteRef:
+        key: test-login-1/username

+ 25 - 0
docs/snippets/1passwordsdk-push-secret.yaml

@@ -0,0 +1,25 @@
+---
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+  name: pushsecret-example # Customisable
+spec:
+  deletionPolicy: Delete
+  refreshInterval: 1h
+  secretStoreRefs:
+    - name: onepassword
+      kind: SecretStore
+  selector:
+    secret:
+      name: source-secret # Source Kubernetes secret
+  data:
+    - match:
+        secretKey: source-key # Source Kubernetes secret key to be pushed
+        remoteRef:
+          remoteKey: 1pw-secret-name # 1Password item/secret name
+          property: password         # (Optional) 1Password field type, default password
+      metadata:
+        apiVersion: kubernetes.external-secrets.io/v1alpha1
+        kind: PushSecretMetadata
+        spec:
+          tags: ["tag1", "tag2"]    # Optional metadata to be pushed with the secret

+ 16 - 0
docs/snippets/1passwordsdk-secret-store.yaml

@@ -0,0 +1,16 @@
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: 1password-sdk
+spec:
+  provider:
+    onepasswordSDK:
+      vault: staging
+      auth:
+        serviceAccountSecretRef:
+          name: onepassword-connect-token-staging
+          key: token
+      integrationInfo: # this is optional and defaulted
+        name: integration-info
+        version: v1

+ 8 - 0
go.mod

@@ -63,6 +63,7 @@ require github.com/1Password/connect-sdk-go v1.5.3
 require (
 	cloud.google.com/go/compute/metadata v0.6.0
 	dario.cat/mergo v1.0.2
+	github.com/1password/onepassword-sdk-go v0.2.1
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
@@ -144,8 +145,10 @@ require (
 	github.com/cyphar/filepath-securejoin v0.4.1 // indirect
 	github.com/danieljoos/wincred v1.2.2 // indirect
 	github.com/djherbis/times v1.6.0 // indirect
+	github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+	github.com/extism/go-sdk v1.7.0 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fxamacker/cbor/v2 v2.8.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.9 // indirect
@@ -161,6 +164,7 @@ require (
 	github.com/go-openapi/validate v0.24.0 // indirect
 	github.com/go-playground/validator/v10 v10.26.0 // indirect
 	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+	github.com/gobwas/glob v0.2.3 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/gofrs/flock v0.12.1 // indirect
 	github.com/golang/glog v1.2.5 // indirect
@@ -172,6 +176,7 @@ require (
 	github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/hashicorp/hcl/v2 v2.23.0 // indirect
+	github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/kevinburke/ssh_config v1.2.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
@@ -198,6 +203,8 @@ require (
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/skeema/knownhosts v1.3.1 // indirect
+	github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
+	github.com/tetratelabs/wazero v1.8.2 // indirect
 	github.com/texttheater/golang-levenshtein v1.0.1 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
@@ -211,6 +218,7 @@ require (
 	go.opentelemetry.io/otel v1.35.0 // indirect
 	go.opentelemetry.io/otel/metric v1.35.0 // indirect
 	go.opentelemetry.io/otel/trace v1.35.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.4.0 // indirect
 	go.uber.org/automaxprocs v1.6.0 // indirect
 	golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
 	golang.org/x/sync v0.14.0 // indirect

+ 16 - 0
go.sum

@@ -56,6 +56,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk=
 github.com/1Password/connect-sdk-go v1.5.3/go.mod h1:5rSymY4oIYtS4G3t0oMkGAXBeoYiukV3vkqlnEjIDJs=
+github.com/1password/onepassword-sdk-go v0.2.1 h1:wwJmjR3UrwYxgAmNpKZ/mHOgFYCz6aQx7NxQ2YCFOL8=
+github.com/1password/onepassword-sdk-go v0.2.1/go.mod h1:R+3/jgPZRbfuXrMCqrl3NM46MMbpc4Zue5S5KRv6yC8=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw=
@@ -295,6 +297,8 @@ github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
 github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
 github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
+github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
+github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
 github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
 github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
 github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
@@ -316,6 +320,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT
 github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
 github.com/external-secrets/sprig/v3 v3.3.0 h1:uO5rmIKSjjONthpCIU8xKbBpAJd0zL/6XFEdC+JsSqU=
 github.com/external-secrets/sprig/v3 v3.3.0/go.mod h1:tvPBN33djer3sQffmfEfcQdL5VYKYmetb4Zbe6wtAq8=
+github.com/extism/go-sdk v1.7.0 h1:yHbSa2JbcF60kjGsYiGEOcClfbknqCJchyh9TRibFWo=
+github.com/extism/go-sdk v1.7.0/go.mod h1:Dhuc1qcD0aqjdqJ3ZDyGdkZPEj/EHKVjbE4P+1XRMqc=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@@ -395,6 +401,8 @@ github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
 github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
 github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
 github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -568,6 +576,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
 github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA=
+github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -806,6 +816,10 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
+github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
+github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
+github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
 github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=
 github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=
 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -874,6 +888,8 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
 go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
 go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
 go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
+go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=

+ 2 - 1
hack/api-docs/mkdocs.yml

@@ -130,7 +130,8 @@ nav:
       - GitLab Variables: provider/gitlab-variables.md
       - Github Actions Secrets: provider/github.md
       - Oracle Vault: provider/oracle-vault.md
-      - 1Password Secrets Automation: provider/1password-automation.md
+      - 1Password Connect Server: provider/1password-automation.md
+      - 1Password SDK: provider/1password-sdk.md
       - Webhook: provider/webhook.md
       - Fake: provider/fake.md
       - senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md

+ 347 - 0
pkg/provider/onepasswordsdk/client.go

@@ -0,0 +1,347 @@
+/*
+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 onepasswordsdk
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/1password/onepassword-sdk-go"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/kube-openapi/pkg/validation/strfmt"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/pkg/utils/metadata"
+)
+
+// ErrKeyNotFound is returned when a key is not found in the 1Password Vaults.
+var ErrKeyNotFound = errors.New("key not found")
+
+type PushSecretMetadataSpec struct {
+	Tags []string `json:"tags,omitempty"`
+}
+
+// GetSecret returns a single secret from the provider.
+// Follows syntax is used for the ref key: https://developer.1password.com/docs/cli/secret-reference-syntax/
+func (p *Provider) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	if ref.Version != "" {
+		return nil, errors.New(errVersionNotImplemented)
+	}
+	key := p.constructRefKey(ref.Key)
+	secret, err := p.client.Secrets().Resolve(ctx, key)
+	if err != nil {
+		return nil, err
+	}
+	return []byte(secret), nil
+}
+
+// Close closes the client connection.
+func (p *Provider) Close(_ context.Context) error {
+	return nil
+}
+
+// DeleteSecret implements Secret Deletion on the provider when PushSecret.spec.DeletionPolicy=Delete.
+func (p *Provider) DeleteSecret(ctx context.Context, ref esv1.PushSecretRemoteRef) error {
+	providerItem, err := p.findItem(ctx, ref.GetRemoteKey())
+	if err != nil {
+		return err
+	}
+
+	providerItem.Fields, err = deleteField(providerItem.Fields, ref.GetProperty())
+	if err != nil {
+		return fmt.Errorf("failed to delete fields: %w", err)
+	}
+
+	// There is a chance that there is an empty item left in the section like this: [{ID: Title:}].
+	if len(providerItem.Sections) == 1 && providerItem.Sections[0].ID == "" && providerItem.Sections[0].Title == "" {
+		providerItem.Sections = nil
+	}
+
+	if len(providerItem.Fields) == 0 && len(providerItem.Files) == 0 && len(providerItem.Sections) == 0 {
+		// Delete the item if there are no fields, files or sections
+		if err = p.client.Items().Delete(ctx, providerItem.VaultID, providerItem.ID); err != nil {
+			return fmt.Errorf("failed to delete item: %w", err)
+		}
+		return nil
+	}
+
+	if _, err = p.client.Items().Put(ctx, providerItem); err != nil {
+		return fmt.Errorf("failed to update item: %w", err)
+	}
+	return nil
+}
+
+func deleteField(fields []onepassword.ItemField, title string) ([]onepassword.ItemField, error) {
+	// This will always iterate over all items,
+	// but it's done to ensure that two fields with the same label
+	// exist resulting in undefined behavior
+	var (
+		found   bool
+		fieldsF = make([]onepassword.ItemField, 0, len(fields))
+	)
+	for _, item := range fields {
+		if item.Title == title {
+			if found {
+				return nil, fmt.Errorf("found multiple labels on item %q", title)
+			}
+			found = true
+			continue
+		}
+		fieldsF = append(fieldsF, item)
+	}
+	return fieldsF, nil
+}
+
+// GetAllSecrets Not Implemented.
+func (p *Provider) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errNotImplemented))
+}
+
+// GetSecretMap implements v1.SecretsClient.
+func (p *Provider) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	if ref.Version != "" {
+		return nil, errors.New(errVersionNotImplemented)
+	}
+
+	// Gets a secret as normal, expecting secret value to be a json object
+	data, err := p.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, fmt.Errorf("error getting secret %s: %w", ref.Key, err)
+	}
+
+	// Maps the json data to a string:string map
+	kv := make(map[string]string)
+	err = json.Unmarshal(data, &kv)
+	if err != nil {
+		return nil, fmt.Errorf("failed to unmarshal data: %w", err)
+	}
+
+	// Converts values in K:V pairs into bytes, while leaving keys as strings
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+
+	return secretData, nil
+}
+
+// createItem creates a new item in the first vault. If no vaults exist, it returns an error.
+func (p *Provider) createItem(ctx context.Context, val []byte, ref esv1.PushSecretData) error {
+	// Get the metadata
+	mdata, err := metadata.ParseMetadataParameters[PushSecretMetadataSpec](ref.GetMetadata())
+	if err != nil {
+		return fmt.Errorf("failed to parse push secret metadata: %w", err)
+	}
+
+	// Get the label
+	label := ref.GetProperty()
+	if label == "" {
+		label = "password"
+	}
+
+	var tags []string
+	if mdata != nil && mdata.Spec.Tags != nil {
+		tags = mdata.Spec.Tags
+	}
+
+	// Create the item
+	_, err = p.client.Items().Create(ctx, onepassword.ItemCreateParams{
+		Category: onepassword.ItemCategoryServer,
+		VaultID:  p.vaultID,
+		Title:    ref.GetRemoteKey(),
+		Fields: []onepassword.ItemField{
+			generateNewItemField(label, string(val)),
+		},
+		Tags: tags,
+	})
+	if err != nil {
+		return fmt.Errorf("failed to create item: %w", err)
+	}
+
+	return nil
+}
+
+// updateFieldValue updates the fields value of an item with the given label.
+// If the label does not exist, a new field is created. If the label exists but
+// the value is different, the value is updated. If the label exists and the
+// value is the same, nothing is done.
+func updateFieldValue(fields []onepassword.ItemField, title, newVal string) ([]onepassword.ItemField, error) {
+	// This will always iterate over all items.
+	// This is done to ensure that two fields with the same label
+	// exist resulting in undefined behavior.
+	var (
+		found bool
+		index int
+	)
+	for i, item := range fields {
+		if item.Title == title {
+			if found {
+				return nil, fmt.Errorf("found multiple labels with the same key")
+			}
+			found = true
+			index = i
+		}
+	}
+	if !found {
+		return append(fields, generateNewItemField(title, newVal)), nil
+	}
+
+	if fields[index].Value != newVal {
+		fields[index].Value = newVal
+	}
+
+	return fields, nil
+}
+
+// generateNewItemField generates a new item field with the given label and value.
+func generateNewItemField(title, newVal string) onepassword.ItemField {
+	field := onepassword.ItemField{
+		Title:     title,
+		Value:     newVal,
+		FieldType: onepassword.ItemFieldTypeConcealed,
+	}
+
+	return field
+}
+
+func (p *Provider) PushSecret(ctx context.Context, secret *corev1.Secret, ref esv1.PushSecretData) error {
+	val, ok := secret.Data[ref.GetSecretKey()]
+	if !ok {
+		return fmt.Errorf("secret %s/%s does not contain a key", secret.Namespace, secret.Name)
+	}
+
+	title := ref.GetRemoteKey()
+	providerItem, err := p.findItem(ctx, title)
+	if errors.Is(err, ErrKeyNotFound) {
+		if err = p.createItem(ctx, val, ref); err != nil {
+			return fmt.Errorf("failed to create item: %w", err)
+		}
+
+		return nil
+	} else if err != nil {
+		return fmt.Errorf("failed to find item: %w", err)
+	}
+
+	// TODO: We are only sending info to a specific label on a 1password item.
+	// We should change this logic eventually to allow pushing whole kubernetes Secrets to 1password as multiple labels
+	// OOTB.
+	label := ref.GetProperty()
+	if label == "" {
+		label = "password"
+	}
+
+	mdata, err := metadata.ParseMetadataParameters[PushSecretMetadataSpec](ref.GetMetadata())
+	if err != nil {
+		return fmt.Errorf("failed to parse push secret metadata: %w", err)
+	}
+	if mdata != nil && mdata.Spec.Tags != nil {
+		providerItem.Tags = mdata.Spec.Tags
+	}
+
+	providerItem.Fields, err = updateFieldValue(providerItem.Fields, label, string(val))
+	if err != nil {
+		return fmt.Errorf("failed to update field with value %s: %w", string(val), err)
+	}
+
+	if _, err = p.client.Items().Put(ctx, providerItem); err != nil {
+		return fmt.Errorf("failed to update item: %w", err)
+	}
+
+	return nil
+}
+
+func (p *Provider) GetVault(ctx context.Context, name string) (string, error) {
+	vaults, err := p.client.VaultsAPI.ListAll(ctx)
+	if err != nil {
+		return "", fmt.Errorf("failed to list vaults: %w", err)
+	}
+
+	for {
+		v, err := vaults.Next()
+		if err != nil {
+			// the only time the iterator returns an error is when it's done.
+			break
+		}
+
+		if v.Title == name {
+			// cache the ID so we don't have to repeat this lookup.
+			p.vaultID = v.ID
+			return v.ID, nil
+		}
+	}
+
+	return "", fmt.Errorf("vault %s not found", name)
+}
+
+func (p *Provider) findItem(ctx context.Context, name string) (onepassword.Item, error) {
+	if strfmt.IsUUID(name) {
+		return p.client.Items().Get(ctx, p.vaultID, name)
+	}
+
+	items, err := p.client.Items().ListAll(ctx, p.vaultID)
+	if err != nil {
+		return onepassword.Item{}, fmt.Errorf("failed to list items: %w", err)
+	}
+
+	// We don't stop
+	var itemUUID string
+	for {
+		v, err := items.Next()
+		// the only time the iterator returns an error is when it's done.
+		if err != nil {
+			break
+		}
+
+		if v.Title == name {
+			if itemUUID != "" {
+				return onepassword.Item{}, fmt.Errorf("found multiple items with name %s", name)
+			}
+			itemUUID = v.ID
+		}
+	}
+
+	if itemUUID == "" {
+		return onepassword.Item{}, ErrKeyNotFound
+	}
+
+	return p.client.Items().Get(ctx, p.vaultID, itemUUID)
+}
+
+// SecretExists Not Implemented.
+func (p *Provider) SecretExists(ctx context.Context, ref esv1.PushSecretRemoteRef) (bool, error) {
+	return false, fmt.Errorf("not implemented")
+}
+
+// Validate checks if the client is configured correctly
+// currently only checks if it is possible to list vaults.
+func (p *Provider) Validate() (esv1.ValidationResult, error) {
+	vaults, err := p.client.Vaults().ListAll(context.Background())
+	if err != nil {
+		return esv1.ValidationResultError, fmt.Errorf("error listing vaults: %w", err)
+	}
+	_, err = vaults.Next()
+	if err != nil {
+		return esv1.ValidationResultError, fmt.Errorf("no vaults found when listing: %w", err)
+	}
+	return esv1.ValidationResultReady, nil
+}
+
+func (p *Provider) constructRefKey(key string) string {
+	// remove any possible leading slashes because the vaultPrefix already contains it.
+	return p.vaultPrefix + strings.TrimPrefix(key, "/")
+}

+ 550 - 0
pkg/provider/onepasswordsdk/client_test.go

@@ -0,0 +1,550 @@
+/*
+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 onepasswordsdk
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/1password/onepassword-sdk-go"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+func TestProviderGetSecret(t *testing.T) {
+	tests := []struct {
+		name        string
+		ref         v1.ExternalSecretDataRemoteRef
+		want        []byte
+		assertError func(t *testing.T, err error)
+		client      func() *onepassword.Client
+	}{
+		{
+			name: "get secret successfully",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					resolveResult: "secret",
+				}
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			ref: v1.ExternalSecretDataRemoteRef{
+				Key: "secret",
+			},
+			want: []byte("secret"),
+		},
+		{
+			name: "get secret with error",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					resolveError: errors.New("fobar"),
+				}
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			assertError: func(t *testing.T, err error) {
+				require.ErrorContains(t, err, "fobar")
+			},
+			ref: v1.ExternalSecretDataRemoteRef{
+				Key: "secret",
+			},
+		},
+		{
+			name: "get secret version not implemented",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					resolveResult: "secret",
+				}
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			ref: v1.ExternalSecretDataRemoteRef{
+				Key:     "secret",
+				Version: "1",
+			},
+			assertError: func(t *testing.T, err error) {
+				require.ErrorContains(t, err, "is not implemented in the 1Password SDK provider")
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &Provider{
+				client:      tt.client(),
+				vaultPrefix: "op://vault/",
+			}
+			got, err := p.GetSecret(context.Background(), tt.ref)
+			tt.assertError(t, err)
+			require.Equal(t, string(got), string(tt.want))
+		})
+	}
+}
+
+func TestProviderGetSecretMap(t *testing.T) {
+	tests := []struct {
+		name        string
+		ref         v1.ExternalSecretDataRemoteRef
+		want        map[string][]byte
+		assertError func(t *testing.T, err error)
+		client      func() *onepassword.Client
+	}{
+		{
+			name: "get secret successfully",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					resolveResult: `{"key": "value"}`,
+				}
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			ref: v1.ExternalSecretDataRemoteRef{
+				Key: "secret",
+			},
+			want: map[string][]byte{
+				"key": []byte("value"),
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &Provider{
+				client:      tt.client(),
+				vaultPrefix: "op://vault/",
+			}
+			got, err := p.GetSecretMap(context.Background(), tt.ref)
+			tt.assertError(t, err)
+			require.Equal(t, got, tt.want)
+		})
+	}
+}
+
+func TestProviderValidate(t *testing.T) {
+	tests := []struct {
+		name        string
+		want        v1.ValidationResult
+		assertError func(t *testing.T, err error)
+		client      func() *onepassword.Client
+		vaultPrefix string
+	}{
+		{
+			name: "validate successfully",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
+						[]onepassword.VaultOverview{
+							{
+								ID:    "test",
+								Title: "test",
+							},
+						},
+					),
+				}
+
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			want: v1.ValidationResultReady,
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			vaultPrefix: "op://vault/",
+		},
+		{
+			name: "validate error",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
+						[]onepassword.VaultOverview{},
+					),
+				}
+
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			want: v1.ValidationResultError,
+			assertError: func(t *testing.T, err error) {
+				require.ErrorContains(t, err, "no vaults found when listing")
+			},
+			vaultPrefix: "op://vault/",
+		},
+		{
+			name: "validate error missing vault prefix",
+			client: func() *onepassword.Client {
+				fc := &fakeClient{
+					listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
+						[]onepassword.VaultOverview{},
+					),
+				}
+
+				return &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+				}
+			},
+			want: v1.ValidationResultError,
+			assertError: func(t *testing.T, err error) {
+				require.ErrorContains(t, err, "no vaults found when listing")
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &Provider{
+				client:      tt.client(),
+				vaultPrefix: tt.vaultPrefix,
+			}
+			got, err := p.Validate()
+			tt.assertError(t, err)
+			require.Equal(t, got, tt.want)
+		})
+	}
+}
+
+func TestPushSecret(t *testing.T) {
+	fc := &fakeClient{
+		listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
+			[]onepassword.VaultOverview{
+				{
+					ID:    "test",
+					Title: "test",
+				},
+			},
+		),
+	}
+
+	tests := []struct {
+		name         string
+		ref          v1alpha1.PushSecretData
+		secret       *corev1.Secret
+		assertError  func(t *testing.T, err error)
+		lister       func() *fakeLister
+		assertLister func(t *testing.T, lister *fakeLister)
+	}{
+		{
+			name: "create is called",
+			lister: func() *fakeLister {
+				return &fakeLister{
+					listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
+						[]onepassword.ItemOverview{},
+					),
+				}
+			},
+			secret: &corev1.Secret{
+				Data: map[string][]byte{
+					"foo": []byte("bar"),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "secret",
+					Namespace: "default",
+				},
+			},
+			ref: v1alpha1.PushSecretData{
+				Match: v1alpha1.PushSecretMatch{
+					SecretKey: "foo",
+					RemoteRef: v1alpha1.PushSecretRemoteRef{
+						RemoteKey: "key",
+					},
+				},
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			assertLister: func(t *testing.T, lister *fakeLister) {
+				assert.True(t, lister.createCalled)
+			},
+		},
+		{
+			name: "update is called",
+			lister: func() *fakeLister {
+				return &fakeLister{
+					listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
+						[]onepassword.ItemOverview{
+							{
+								ID:       "test-item-id",
+								Title:    "key",
+								Category: "login",
+								VaultID:  "vault-id",
+							},
+						},
+					),
+				}
+			},
+			secret: &corev1.Secret{
+				Data: map[string][]byte{
+					"foo": []byte("bar"),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "secret",
+					Namespace: "default",
+				},
+			},
+			ref: v1alpha1.PushSecretData{
+				Match: v1alpha1.PushSecretMatch{
+					SecretKey: "foo",
+					RemoteRef: v1alpha1.PushSecretRemoteRef{
+						RemoteKey: "key",
+					},
+				},
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			assertLister: func(t *testing.T, lister *fakeLister) {
+				assert.True(t, lister.putCalled)
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := context.Background()
+			lister := tt.lister()
+			p := &Provider{
+				client: &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+					ItemsAPI:   lister,
+				},
+			}
+
+			err := p.PushSecret(ctx, tt.secret, tt.ref)
+			tt.assertError(t, err)
+			tt.assertLister(t, lister)
+		})
+	}
+}
+
+func TestDeleteItemField(t *testing.T) {
+	fc := &fakeClient{
+		listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
+			[]onepassword.VaultOverview{
+				{
+					ID:    "test",
+					Title: "test",
+				},
+			},
+		),
+	}
+
+	testCases := []struct {
+		name         string
+		lister       func() *fakeLister
+		ref          *v1alpha1.PushSecretRemoteRef
+		assertError  func(t *testing.T, err error)
+		assertLister func(t *testing.T, lister *fakeLister)
+	}{
+		{
+			name: "update is called",
+			ref: &v1alpha1.PushSecretRemoteRef{
+				RemoteKey: "key",
+				Property:  "password",
+			},
+			assertLister: func(t *testing.T, lister *fakeLister) {
+				require.True(t, lister.putCalled)
+			},
+			lister: func() *fakeLister {
+				fl := &fakeLister{
+					listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
+						[]onepassword.ItemOverview{
+							{
+								ID:       "test-item-id",
+								Title:    "key",
+								Category: "login",
+								VaultID:  "vault-id",
+							},
+						},
+					),
+					getResult: onepassword.Item{
+						ID:       "test-item-id",
+						Title:    "key",
+						Category: "login",
+						VaultID:  "vault-id",
+						Fields: []onepassword.ItemField{
+							{
+								ID:        "field-1",
+								Title:     "password",
+								FieldType: onepassword.ItemFieldTypeConcealed,
+								Value:     "password",
+							},
+							{
+								ID:        "field-2",
+								Title:     "other-field",
+								FieldType: onepassword.ItemFieldTypeConcealed,
+								Value:     "username",
+							},
+						},
+					},
+				}
+
+				return fl
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+		},
+		{
+			name: "delete is called",
+			ref: &v1alpha1.PushSecretRemoteRef{
+				RemoteKey: "key",
+				Property:  "password",
+			},
+			assertLister: func(t *testing.T, lister *fakeLister) {
+				require.True(t, lister.deleteCalled, "delete should have been called as the item should have existed")
+			},
+			lister: func() *fakeLister {
+				fl := &fakeLister{
+					listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
+						[]onepassword.ItemOverview{
+							{
+								ID:       "test-item-id",
+								Title:    "key",
+								Category: "login",
+								VaultID:  "vault-id",
+							},
+						},
+					),
+					getResult: onepassword.Item{
+						ID:       "test-item-id",
+						Title:    "key",
+						Category: "login",
+						VaultID:  "vault-id",
+						Fields: []onepassword.ItemField{
+							{
+								ID:        "field-1",
+								Title:     "password",
+								FieldType: onepassword.ItemFieldTypeConcealed,
+								Value:     "password",
+							},
+						},
+					},
+				}
+
+				return fl
+			},
+			assertError: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			ctx := context.Background()
+			lister := testCase.lister()
+			p := &Provider{
+				client: &onepassword.Client{
+					SecretsAPI: fc,
+					VaultsAPI:  fc,
+					ItemsAPI:   lister,
+				},
+			}
+
+			testCase.assertError(t, p.DeleteSecret(ctx, testCase.ref))
+			testCase.assertLister(t, lister)
+		})
+	}
+}
+
+type fakeLister struct {
+	listAllResult *onepassword.Iterator[onepassword.ItemOverview]
+	createCalled  bool
+	putCalled     bool
+	deleteCalled  bool
+	getResult     onepassword.Item
+}
+
+func (f *fakeLister) Create(ctx context.Context, params onepassword.ItemCreateParams) (onepassword.Item, error) {
+	f.createCalled = true
+	return onepassword.Item{}, nil
+}
+
+func (f *fakeLister) Get(ctx context.Context, vaultID, itemID string) (onepassword.Item, error) {
+	return f.getResult, nil
+}
+
+func (f *fakeLister) Put(ctx context.Context, item onepassword.Item) (onepassword.Item, error) {
+	f.putCalled = true
+	return onepassword.Item{}, nil
+}
+
+func (f *fakeLister) Delete(ctx context.Context, vaultID, itemID string) error {
+	f.deleteCalled = true
+	return nil
+}
+
+func (f *fakeLister) Archive(ctx context.Context, vaultID, itemID string) error {
+	return nil
+}
+
+func (f *fakeLister) ListAll(ctx context.Context, vaultID string) (*onepassword.Iterator[onepassword.ItemOverview], error) {
+	return f.listAllResult, nil
+}
+
+func (f *fakeLister) Shares() onepassword.ItemsSharesAPI {
+	return nil
+}
+
+func (f *fakeLister) Files() onepassword.ItemsFilesAPI {
+	return nil
+}
+
+type fakeClient struct {
+	resolveResult   string
+	resolveError    error
+	resolveAll      onepassword.ResolveAllResponse
+	resolveAllError error
+	listAllResult   *onepassword.Iterator[onepassword.VaultOverview]
+	listAllError    error
+}
+
+func (f *fakeClient) ListAll(ctx context.Context) (*onepassword.Iterator[onepassword.VaultOverview], error) {
+	return f.listAllResult, f.listAllError
+}
+
+func (f *fakeClient) Resolve(ctx context.Context, secretReference string) (string, error) {
+	return f.resolveResult, f.resolveError
+}
+
+func (f *fakeClient) ResolveAll(ctx context.Context, secretReferences []string) (onepassword.ResolveAllResponse, error) {
+	return f.resolveAll, f.resolveAllError
+}
+
+var _ onepassword.SecretsAPI = &fakeClient{}
+var _ onepassword.VaultsAPI = &fakeClient{}
+var _ onepassword.ItemsAPI = &fakeLister{}

+ 130 - 0
pkg/provider/onepasswordsdk/provider.go

@@ -0,0 +1,130 @@
+/*
+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 onepasswordsdk
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/1password/onepassword-sdk-go"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
+)
+
+const (
+	errOnePasswordSdkStore                              = "received invalid 1PasswordSdk SecretStore resource: %w"
+	errOnePasswordSdkStoreNilSpec                       = "nil spec"
+	errOnePasswordSdkStoreNilSpecProvider               = "nil spec.provider"
+	errOnePasswordSdkStoreNilSpecProviderOnePasswordSdk = "nil spec.provider.onepasswordsdk"
+	errOnePasswordSdkStoreMissingRefName                = "missing: spec.provider.onepasswordsdk.auth.secretRef.serviceAccountTokenSecretRef.name"
+	errOnePasswordSdkStoreMissingRefKey                 = "missing: spec.provider.onepasswordsdk.auth.secretRef.serviceAccountTokenSecretRef.key"
+	errOnePasswordSdkStoreMissingVaultKey               = "missing: spec.provider.onepasswordsdk.vault"
+	errVersionNotImplemented                            = "'remoteRef.version' is not implemented in the 1Password SDK provider"
+	errNotImplemented                                   = "not implemented"
+)
+
+type Provider struct {
+	client      *onepassword.Client
+	vaultPrefix string
+	vaultID     string
+}
+
+func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
+	config := store.GetSpec().Provider.OnePasswordSDK
+	serviceAccountToken, err := resolvers.SecretKeyRef(
+		ctx,
+		kube,
+		store.GetKind(),
+		namespace,
+		&config.Auth.ServiceAccountSecretRef,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	if config.IntegrationInfo == nil {
+		config.IntegrationInfo = &esv1.IntegrationInfo{
+			Name:    "1Password SDK",
+			Version: "v1.0.0",
+		}
+	}
+
+	c, err := onepassword.NewClient(
+		ctx,
+		onepassword.WithServiceAccountToken(serviceAccountToken),
+		onepassword.WithIntegrationInfo(config.IntegrationInfo.Name, config.IntegrationInfo.Version),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	p.client = c
+	p.vaultPrefix = "op://" + config.Vault + "/"
+
+	vaultID, err := p.GetVault(ctx, config.Vault)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get store ID: %w", err)
+	}
+	p.vaultID = vaultID
+
+	return p, nil
+}
+
+func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
+	storeSpec := store.GetSpec()
+	if storeSpec == nil {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreNilSpec))
+	}
+	if storeSpec.Provider == nil {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreNilSpecProvider))
+	}
+	if storeSpec.Provider.OnePasswordSDK == nil {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreNilSpecProviderOnePasswordSdk))
+	}
+
+	config := storeSpec.Provider.OnePasswordSDK
+	if config.Auth.ServiceAccountSecretRef.Name == "" {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreMissingRefName))
+	}
+	if config.Auth.ServiceAccountSecretRef.Key == "" {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreMissingRefKey))
+	}
+
+	if config.Vault == "" {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, errors.New(errOnePasswordSdkStoreMissingVaultKey))
+	}
+
+	// check namespace compared to kind
+	if err := utils.ValidateSecretSelector(store, config.Auth.ServiceAccountSecretRef); err != nil {
+		return nil, fmt.Errorf(errOnePasswordSdkStore, err)
+	}
+
+	return nil, nil
+}
+
+func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
+	return esv1.SecretStoreReadWrite
+}
+
+func init() {
+	esv1.Register(&Provider{}, &esv1.SecretStoreProvider{
+		OnePasswordSDK: &esv1.OnePasswordSDKProvider{},
+	}, esv1.MaintenanceStatusMaintained)
+}

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

@@ -40,6 +40,7 @@ import (
 	_ "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/onepassword"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/onepasswordsdk"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/passbolt"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/passworddepot"

+ 10 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -412,6 +412,16 @@ spec:
             namespace: string
       connectHost: string
       vaults: {}
+    onepasswordSDK:
+      auth:
+        serviceAccountSecretRef:
+          key: string
+          name: string
+          namespace: string
+      integrationInfo:
+        name: "1Password SDK"
+        version: "v1.0.0"
+      vault: string
     oracle:
       auth:
         secretRef:

+ 10 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -412,6 +412,16 @@ spec:
             namespace: string
       connectHost: string
       vaults: {}
+    onepasswordSDK:
+      auth:
+        serviceAccountSecretRef:
+          key: string
+          name: string
+          namespace: string
+      integrationInfo:
+        name: "1Password SDK"
+        version: "v1.0.0"
+      vault: string
     oracle:
       auth:
         secretRef: