Browse Source

Add device42 provider (#3571)

smcavallo 1 year ago
parent
commit
d29c001d37

+ 4 - 4
Makefile

@@ -257,22 +257,22 @@ docker.promote: ## Promote the docker image to the registry
 # ====================================================================================
 # ====================================================================================
 # Terraform
 # Terraform
 
 
-tf.plan.%: ## Runs terrform plan for a provider
+tf.plan.%: ## Runs terraform plan for a provider
 	@cd $(TF_DIR)/$*; \
 	@cd $(TF_DIR)/$*; \
 	terraform init; \
 	terraform init; \
 	terraform plan
 	terraform plan
 
 
-tf.apply.%: ## Runs terrform apply for a provider
+tf.apply.%: ## Runs terraform apply for a provider
 	@cd $(TF_DIR)/$*; \
 	@cd $(TF_DIR)/$*; \
 	terraform init; \
 	terraform init; \
 	terraform apply -auto-approve
 	terraform apply -auto-approve
 
 
-tf.destroy.%: ## Runs terrform destroy for a provider
+tf.destroy.%: ## Runs terraform destroy for a provider
 	@cd $(TF_DIR)/$*; \
 	@cd $(TF_DIR)/$*; \
 	terraform init; \
 	terraform init; \
 	terraform destroy -auto-approve
 	terraform destroy -auto-approve
 
 
-tf.show.%: ## Runs terrform show for a provider and outputs to a file
+tf.show.%: ## Runs terraform show for a provider and outputs to a file
 	@cd $(TF_DIR)/$*; \
 	@cd $(TF_DIR)/$*; \
 	terraform init; \
 	terraform init; \
 	terraform plan -out tfplan.binary; \
 	terraform plan -out tfplan.binary; \

+ 38 - 0
apis/externalsecrets/v1beta1/secretstore_device42_types.go

@@ -0,0 +1,38 @@
+/*
+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"
+)
+
+// Device42Provider configures a store to sync secrets with a Device42 instance.
+type Device42Provider struct {
+	// URL configures the Device42 instance URL.
+	Host string `json:"host"`
+
+	// Auth configures how secret-manager authenticates with a Device42 instance.
+	Auth Device42Auth `json:"auth"`
+}
+
+type Device42Auth struct {
+	SecretRef Device42SecretRef `json:"secretRef"`
+}
+
+type Device42SecretRef struct {
+	// Username / Password is used for authentication.
+	// +optional
+	Credentials esmeta.SecretKeySelector `json:"credentials,omitempty"`
+}

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

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

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

@@ -868,6 +868,54 @@ func (in *DelineaProviderSecretRef) DeepCopy() *DelineaProviderSecretRef {
 }
 }
 
 
 // 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 *Device42Auth) DeepCopyInto(out *Device42Auth) {
+	*out = *in
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Device42Auth.
+func (in *Device42Auth) DeepCopy() *Device42Auth {
+	if in == nil {
+		return nil
+	}
+	out := new(Device42Auth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Device42Provider) DeepCopyInto(out *Device42Provider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Device42Provider.
+func (in *Device42Provider) DeepCopy() *Device42Provider {
+	if in == nil {
+		return nil
+	}
+	out := new(Device42Provider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Device42SecretRef) DeepCopyInto(out *Device42SecretRef) {
+	*out = *in
+	in.Credentials.DeepCopyInto(&out.Credentials)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Device42SecretRef.
+func (in *Device42SecretRef) DeepCopy() *Device42SecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(Device42SecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
 func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
 	*out = *in
 	*out = *in
 	in.SecretRef.DeepCopyInto(&out.SecretRef)
 	in.SecretRef.DeepCopyInto(&out.SecretRef)
@@ -2357,6 +2405,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(PassboltProvider)
 		*out = new(PassboltProvider)
 		(*in).DeepCopyInto(*out)
 		(*in).DeepCopyInto(*out)
 	}
 	}
+	if in.Device42 != nil {
+		in, out := &in.Device42, &out.Device42
+		*out = new(Device42Provider)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Infisical != nil {
 	if in.Infisical != nil {
 		in, out := &in.Infisical, &out.Infisical
 		in, out := &in.Infisical, &out.Infisical
 		*out = new(InfisicalProvider)
 		*out = new(InfisicalProvider)

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

@@ -2569,6 +2569,45 @@ spec:
                     - clientSecret
                     - clientSecret
                     - tenant
                     - tenant
                     type: object
                     type: object
+                  device42:
+                    description: Device42 configures this store to sync secrets using
+                      the Device42 provider
+                    properties:
+                      auth:
+                        description: Auth configures how secret-manager authenticates
+                          with a Device42 instance.
+                        properties:
+                          secretRef:
+                            properties:
+                              credentials:
+                                description: Username / Password is used for authentication.
+                                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
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      host:
+                        description: URL configures the Device42 instance URL.
+                        type: string
+                    required:
+                    - auth
+                    - host
+                    type: object
                   doppler:
                   doppler:
                     description: Doppler configures this store to sync secrets using
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider
                       the Doppler provider

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

@@ -2569,6 +2569,45 @@ spec:
                     - clientSecret
                     - clientSecret
                     - tenant
                     - tenant
                     type: object
                     type: object
+                  device42:
+                    description: Device42 configures this store to sync secrets using
+                      the Device42 provider
+                    properties:
+                      auth:
+                        description: Auth configures how secret-manager authenticates
+                          with a Device42 instance.
+                        properties:
+                          secretRef:
+                            properties:
+                              credentials:
+                                description: Username / Password is used for authentication.
+                                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
+                            type: object
+                        required:
+                        - secretRef
+                        type: object
+                      host:
+                        description: URL configures the Device42 instance URL.
+                        type: string
+                    required:
+                    - auth
+                    - host
+                    type: object
                   doppler:
                   doppler:
                     description: Doppler configures this store to sync secrets using
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider
                       the Doppler provider

+ 72 - 0
deploy/crds/bundle.yaml

@@ -3056,6 +3056,42 @@ spec:
                         - clientSecret
                         - clientSecret
                         - tenant
                         - tenant
                       type: object
                       type: object
+                    device42:
+                      description: Device42 configures this store to sync secrets using the Device42 provider
+                      properties:
+                        auth:
+                          description: Auth configures how secret-manager authenticates with a Device42 instance.
+                          properties:
+                            secretRef:
+                              properties:
+                                credentials:
+                                  description: Username / Password is used for authentication.
+                                  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
+                              type: object
+                          required:
+                            - secretRef
+                          type: object
+                        host:
+                          description: URL configures the Device42 instance URL.
+                          type: string
+                      required:
+                        - auth
+                        - host
+                      type: object
                     doppler:
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:
                       properties:
@@ -8502,6 +8538,42 @@ spec:
                         - clientSecret
                         - clientSecret
                         - tenant
                         - tenant
                       type: object
                       type: object
+                    device42:
+                      description: Device42 configures this store to sync secrets using the Device42 provider
+                      properties:
+                        auth:
+                          description: Auth configures how secret-manager authenticates with a Device42 instance.
+                          properties:
+                            secretRef:
+                              properties:
+                                credentials:
+                                  description: Username / Password is used for authentication.
+                                  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
+                              type: object
+                          required:
+                            - secretRef
+                          type: object
+                        host:
+                          description: URL configures the Device42 instance URL.
+                          type: string
+                      required:
+                        - auth
+                        - host
+                      type: object
                     doppler:
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:
                       properties:

+ 119 - 0
docs/api/spec.md

@@ -2234,6 +2234,111 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tr>
 </tbody>
 </tbody>
 </table>
 </table>
+<h3 id="external-secrets.io/v1beta1.Device42Auth">Device42Auth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.Device42Provider">Device42Provider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.Device42SecretRef">
+Device42SecretRef
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.Device42Provider">Device42Provider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>Device42Provider configures a store to sync secrets with a Device42 instance.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>host</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>URL configures the Device42 instance URL.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.Device42Auth">
+Device42Auth
+</a>
+</em>
+</td>
+<td>
+<p>Auth configures how secret-manager authenticates with a Device42 instance.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.Device42SecretRef">Device42SecretRef
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.Device42Auth">Device42Auth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>credentials</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>
+<em>(Optional)</em>
+<p>Username / Password is used for authentication.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.DopplerAuth">DopplerAuth
 <h3 id="external-secrets.io/v1beta1.DopplerAuth">DopplerAuth
 </h3>
 </h3>
 <p>
 <p>
@@ -6192,6 +6297,20 @@ PassboltProvider
 </tr>
 </tr>
 <tr>
 <tr>
 <td>
 <td>
+<code>device42</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.Device42Provider">
+Device42Provider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Device42 configures this store to sync secrets using the Device42 provider</p>
+</td>
+</tr>
+<tr>
+<td>
 <code>infisical</code></br>
 <code>infisical</code></br>
 <em>
 <em>
 <a href="#external-secrets.io/v1beta1.InfisicalProvider">
 <a href="#external-secrets.io/v1beta1.InfisicalProvider">

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

@@ -56,6 +56,7 @@ The following table describes the stability level of each provider and who's res
 | [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)                                                                                       |
 | [Infisical](https://external-secrets.io/latest/provider/infisical)                                         |   alpha   | [@akhilmhdh](https://github.com/akhilmhdh)                                                                                       |
+| [Device42](https://external-secrets.io/latest/provider/device42)                                           |   alpha   |                                                                                                                                                   |
 
 
 ## Provider Feature Support
 ## Provider Feature Support
 
 
@@ -86,6 +87,7 @@ The following table show the support for features across different providers.
 | Pulumi ESC                |      x       |              |                      |                         |        x         |             |                             |
 | Pulumi ESC                |      x       |              |                      |                         |        x         |             |                             |
 | Passbolt                  |      x       |              |                      |                         |        x         |             |                             |
 | Passbolt                  |      x       |              |                      |                         |        x         |             |                             |
 | Infisical                 |      x       |              |                      |            x            |        x         |             |                             |
 | Infisical                 |      x       |              |                      |            x            |        x         |             |                             |
+| Device42                  |              |              |                      |                         |        x         |             |                             |
 
 
 ## Support Policy
 ## Support Policy
 
 

+ 58 - 0
docs/provider/device42.md

@@ -0,0 +1,58 @@
+External Secrets Operator integrates with [Device42 API](https://api.device42.com/#!/Passwords/getPassword) to sync Device42 secrets into a Kubernetes cluster.
+
+
+### Authentication
+
+`username` and `password` is required to talk to the Device42 API.
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: device42-credentials
+data:
+  username: dGVzdA== # "test"
+  password: dGVzdA== # "test"
+```
+
+### Creating a SecretStore
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: device42-secret-store
+spec:
+  provider:
+    device42:
+      host: <DEVICE42_HOSTNAME>
+      auth:
+        secretRef:
+          credentials:
+            name: <NAME_OF_KUBE_SECRET>
+            key: <KEY_IN_KUBE_SECRET>
+            namespace: <kube-system>
+```
+
+### Referencing Secrets
+
+Secrets can be referenced by defining the `key` containing the Id of the secret.
+The `password` field is return from device42
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: device42-external-secret
+spec:
+  refreshInterval: 5m
+  secretStoreRef:
+    kind: SecretStore
+    name: device42-secret-store
+  target:
+    name: <K8s_SECRET_NAME_TO_MANAGE>
+  data:
+  - secretKey: <KEY_NAME_WITHIN_KUBE_SECRET>
+    remoteRef:
+      key: <DEVICE42_SECRET_ID>
+```

+ 16 - 0
docs/snippets/device42-external-secret.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: device42-find-by-id
+spec:
+  refreshInterval: 10s
+  secretStoreRef:
+    # This name must match the metadata.name in the `SecretStore`
+    name: device42
+    kind: SecretStore
+  target:
+    name: k8s-secret-to-be-created
+  data:
+    - secretKey: K8S_PASSWORD
+      remoteRef:
+        key: "12345"

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

@@ -95,6 +95,7 @@ nav:
       - Azure Key Vault: provider/azure-key-vault.md
       - Azure Key Vault: provider/azure-key-vault.md
       - Chef: provider/chef.md
       - Chef: provider/chef.md
       - CyberArk Conjur: provider/conjur.md
       - CyberArk Conjur: provider/conjur.md
+      - Device42: provider/device42.md
       - Google Cloud Secret Manager: provider/google-secrets-manager.md
       - Google Cloud Secret Manager: provider/google-secrets-manager.md
       - HashiCorp Vault: provider/hashicorp-vault.md
       - HashiCorp Vault: provider/hashicorp-vault.md
       - Kubernetes: provider/kubernetes.md
       - Kubernetes: provider/kubernetes.md

+ 182 - 0
pkg/provider/device42/device42.go

@@ -0,0 +1,182 @@
+/*
+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 device42
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	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"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+const (
+	errNotImplemented                         = "not implemented"
+	errUninitializedProvider                  = "unable to get device42 client"
+	errCredSecretName                         = "credentials are empty"
+	errInvalidClusterStoreMissingSAKNamespace = "invalid clusterStore missing SAK namespace"
+	errFetchSAKSecret                         = "couldn't find secret on cluster: %w"
+	errMissingSAK                             = "missing credentials while setting auth"
+)
+
+type Client interface {
+	GetSecret(secretID string) (D42Password, error)
+}
+
+// Device42 Provider struct with reference to a Device42 client.
+type Device42 struct {
+	client Client
+}
+
+func (p *Device42) ValidateStore(esv1beta1.GenericStore) (admission.Warnings, error) {
+	return nil, nil
+}
+
+func (p *Device42) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
+// Client for interacting with kubernetes.
+type device42Client struct {
+	kube      kclient.Client
+	store     *esv1beta1.Device42Provider
+	namespace string
+	storeKind string
+}
+type Provider struct{}
+
+func (c *device42Client) getAuth(ctx context.Context) (string, string, error) {
+	credentialsSecret := &corev1.Secret{}
+	credentialsSecretName := c.store.Auth.SecretRef.Credentials.Name
+	if credentialsSecretName == "" {
+		return "", "", fmt.Errorf(errCredSecretName)
+	}
+	objectKey := types.NamespacedName{
+		Name:      credentialsSecretName,
+		Namespace: c.namespace,
+	}
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if c.storeKind == esv1beta1.ClusterSecretStoreKind {
+		if c.store.Auth.SecretRef.Credentials.Namespace == nil {
+			return "", "", fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
+		}
+		objectKey.Namespace = *c.store.Auth.SecretRef.Credentials.Namespace
+	}
+
+	err := c.kube.Get(ctx, objectKey, credentialsSecret)
+	if err != nil {
+		return "", "", fmt.Errorf(errFetchSAKSecret, err)
+	}
+
+	username := credentialsSecret.Data["username"]
+	password := credentialsSecret.Data["password"]
+	if len(username) == 0 || len(password) == 0 {
+		return "", "", fmt.Errorf(errMissingSAK)
+	}
+
+	return string(username), string(password), nil
+}
+
+// NewDevice42Provider returns a reference to a new instance of a 'Device42' struct.
+func NewDevice42Provider() *Device42 {
+	return &Device42{}
+}
+
+func (p *Device42) 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.Device42 == nil {
+		return nil, fmt.Errorf("no store type or wrong store type")
+	}
+	storeSpecDevice42 := storeSpec.Provider.Device42
+
+	cliStore := device42Client{
+		kube:      kube,
+		store:     storeSpecDevice42,
+		namespace: namespace,
+		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
+	}
+
+	username, password, err := cliStore.getAuth(ctx)
+	if err != nil {
+		return nil, err
+	}
+	// Create a new client using credentials and options
+	p.client = NewAPI(storeSpecDevice42.Host, username, password, "443")
+
+	return p, nil
+}
+
+func (p *Device42) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
+	return false, fmt.Errorf(errNotImplemented)
+}
+
+func (p *Device42) Validate() (esv1beta1.ValidationResult, error) {
+	timeout := 15 * time.Second
+	url := fmt.Sprintf("https://%s:%s", p.client.(*API).baseURL, p.client.(*API).hostPort)
+
+	if err := utils.NetworkValidate(url, timeout); err != nil {
+		return esv1beta1.ValidationResultError, err
+	}
+	return esv1beta1.ValidationResultReady, nil
+}
+
+func (p *Device42) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
+	return fmt.Errorf(errNotImplemented)
+}
+
+func (p *Device42) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, fmt.Errorf(errNotImplemented)
+}
+
+func (p *Device42) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
+	return fmt.Errorf(errNotImplemented)
+}
+
+func (p *Device42) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	if utils.IsNil(p.client) {
+		return nil, fmt.Errorf(errUninitializedProvider)
+	}
+
+	data, err := p.client.GetSecret(ref.Key)
+	if err != nil {
+		return nil, err
+	}
+	return []byte(data.Password), nil
+}
+
+func (p *Device42) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	data, err := p.client.GetSecret(ref.Key)
+	if err != nil {
+		return nil, fmt.Errorf("error getting secret %s: %w", ref.Key, err)
+	}
+
+	return data.ToMap(), nil
+}
+
+func (p *Device42) Close(_ context.Context) error {
+	return nil
+}
+
+func init() {
+	esv1beta1.Register(&Device42{}, &esv1beta1.SecretStoreProvider{
+		Device42: &esv1beta1.Device42Provider{},
+	})
+}

+ 130 - 0
pkg/provider/device42/device42_api.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 device42
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strconv"
+	"time"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+const (
+	DoRequestError         = "error: do request: %w"
+	errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
+)
+
+type HTTPClient interface {
+	Do(*http.Request) (*http.Response, error)
+}
+
+type API struct {
+	client   HTTPClient
+	baseURL  string
+	hostPort string
+	password string
+	username string
+}
+
+type D42PasswordResponse struct {
+	Passwords []D42Password
+}
+
+type D42Password struct {
+	Password string `json:"password"`
+	ID       int    `json:"id"`
+}
+
+func NewAPI(baseURL, username, password, hostPort string) *API {
+	api := &API{
+		baseURL:  baseURL,
+		hostPort: hostPort,
+		username: username,
+		password: password,
+	}
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
+	}
+
+	api.client = &http.Client{Transport: tr}
+	return api
+}
+
+func (api *API) doAuthenticatedRequest(r *http.Request) (*http.Response, error) {
+	r.SetBasicAuth(api.username, api.password)
+	return api.client.Do(r)
+}
+
+func ReadAndUnmarshal(resp *http.Response, target any) error {
+	var buf bytes.Buffer
+	defer func() {
+		err := resp.Body.Close()
+		if err != nil {
+			return
+		}
+	}()
+	if resp.StatusCode < 200 || resp.StatusCode > 299 {
+		return fmt.Errorf("failed to authenticate with the given credentials: %d %s", resp.StatusCode, buf.String())
+	}
+	_, err := buf.ReadFrom(resp.Body)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(buf.Bytes(), target)
+}
+
+func (api *API) GetSecret(secretID string) (D42Password, error) {
+	// https://api.device42.com/#!/Passwords/getPassword
+	endpointURL := fmt.Sprintf("https://%s:%s/api/1.0/passwords/?id=%s&plain_text=yes", api.baseURL, api.hostPort, secretID)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
+	defer cancel()
+	readSecretRequest, err := http.NewRequestWithContext(ctx, "GET", endpointURL, http.NoBody)
+	if err != nil {
+		return D42Password{}, fmt.Errorf("error: creating secrets request: %w", err)
+	}
+
+	respSecretRead, err := api.doAuthenticatedRequest(readSecretRequest) //nolint:bodyclose // linters bug
+	if err != nil {
+		return D42Password{}, fmt.Errorf(DoRequestError, err)
+	}
+
+	d42PasswordResponse := D42PasswordResponse{}
+	err = ReadAndUnmarshal(respSecretRead, &d42PasswordResponse)
+	if err != nil {
+		return D42Password{}, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+	if len(d42PasswordResponse.Passwords) == 0 {
+		return D42Password{}, err
+	}
+	// There should only be one response
+	return d42PasswordResponse.Passwords[0], err
+}
+
+func (api *API) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	return nil, fmt.Errorf(errNotImplemented)
+}
+
+func (s D42Password) ToMap() map[string][]byte {
+	m := make(map[string][]byte)
+	m["password"] = []byte(s.Password)
+	m["id"] = []byte(strconv.Itoa(s.ID))
+	return m
+}

+ 127 - 0
pkg/provider/device42/device42_api_test.go

@@ -0,0 +1,127 @@
+/*
+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 device42
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http"
+	"reflect"
+	"testing"
+
+	fakedevice42 "github.com/external-secrets/external-secrets/pkg/provider/device42/fake"
+)
+
+const device42PasswordID = "12345"
+
+func d42PasswordResponse() D42PasswordResponse {
+	return D42PasswordResponse{Passwords: []D42Password{d42Password()}}
+}
+
+func d42Password() D42Password {
+	return D42Password{
+		Password: "test_Password",
+		ID:       12345,
+	}
+}
+
+func TestDevice42ApiGetSecret(t *testing.T) {
+	type fields struct {
+		funcStack []func(req *http.Request) (*http.Response, error)
+	}
+	type args struct {
+		secretID string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    D42Password
+		wantErr bool
+	}{
+		{
+			name: "get secret",
+			fields: fields{
+				funcStack: []func(req *http.Request) (*http.Response, error){
+					createResponder(d42PasswordResponse(), true), //nolint:bodyclose
+				},
+			},
+			args: args{
+				secretID: device42PasswordID,
+			},
+			want:    d42Password(),
+			wantErr: false,
+		},
+		{
+			name: "bad response on secret entry",
+			fields: fields{
+				funcStack: []func(req *http.Request) (*http.Response, error){
+					createResponder([]byte("bad response body"), false), //nolint:bodyclose // linters bug
+				},
+			},
+			args: args{
+				secretID: device42PasswordID,
+			},
+			want:    D42Password{},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			api := &API{
+				client: &fakedevice42.MockClient{
+					FuncStack: tt.fields.funcStack,
+				},
+				baseURL:  "localhost",
+				hostPort: "8714",
+				password: "test",
+				username: "test",
+			}
+			got, err := api.GetSecret(tt.args.secretID)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Device42.GetSecret() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Device42.GetSecret() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func createResponder(payload any, withMarshal bool) func(*http.Request) (*http.Response, error) {
+	return func(req *http.Request) (*http.Response, error) {
+		var payloadBytes []byte
+		if withMarshal {
+			payloadBytes, _ = json.Marshal(payload)
+		} else {
+			payloadBytes = payload.([]byte)
+		}
+		res := http.Response{
+			Status:     "OK",
+			StatusCode: http.StatusOK,
+			Body:       &closeableBuffer{bytes.NewReader(payloadBytes)},
+		}
+		return &res, nil
+	}
+}
+
+type closeableBuffer struct {
+	*bytes.Reader
+}
+
+func (cb *closeableBuffer) Close() error {
+	// Here you can add any cleanup code if needed
+	return nil
+}

+ 31 - 0
pkg/provider/device42/fake/fake.go

@@ -0,0 +1,31 @@
+/*
+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 fake
+
+import "net/http"
+
+// MockClient is the mock client.
+type MockClient struct {
+	index     int
+	FuncStack []func(req *http.Request) (*http.Response, error)
+}
+
+// Do is the mock client's `Do` func.
+func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
+	res, err := m.FuncStack[m.index](req)
+	m.index++
+
+	return res, err
+}

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

@@ -24,6 +24,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/chef"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/chef"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/device42"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/fortanix"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/fortanix"