Browse Source

SSHKey generator (#5083)

* SSHKey generator

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Doc for genrators

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Correct Kind in GeneratorRef

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Correct example

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* keySize field is nullable; only rsa and ed25519 are valid

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Correct test

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Correct example

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

* Snapshot for helm

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>

---------

Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>
Piotr Roszatycki 8 months ago
parent
commit
e2f4be0963

+ 1 - 1
apis/externalsecrets/v1/externalsecret_types.go

@@ -484,7 +484,7 @@ type GeneratorRef struct {
 	APIVersion string `json:"apiVersion,omitempty"`
 
 	// Specify the Kind of the generator resource
-	// +kubebuilder:validation:Enum=ACRAccessToken;ClusterGenerator;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana;MFA
+	// +kubebuilder:validation:Enum=ACRAccessToken;ClusterGenerator;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;SSHKey;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana;MFA
 	Kind string `json:"kind"`
 
 	// Specify the name of the generator resource

+ 1 - 1
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -442,7 +442,7 @@ type GeneratorRef struct {
 	APIVersion string `json:"apiVersion,omitempty"`
 
 	// Specify the Kind of the generator resource
-	// +kubebuilder:validation:Enum=ACRAccessToken;ClusterGenerator;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana
+	// +kubebuilder:validation:Enum=ACRAccessToken;ClusterGenerator;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;SSHKey;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana
 	Kind string `json:"kind"`
 
 	// Specify the name of the generator resource

+ 2 - 0
apis/generators/v1alpha1/register.go

@@ -42,6 +42,7 @@ var (
 	GCRAccessTokenKind        = reflect.TypeOf(GCRAccessToken{}).Name()
 	ACRAccessTokenKind        = reflect.TypeOf(ACRAccessToken{}).Name()
 	PasswordKind              = reflect.TypeOf(Password{}).Name()
+	SSHKeyKind                = reflect.TypeOf(SSHKey{}).Name()
 	WebhookKind               = reflect.TypeOf(Webhook{}).Name()
 	FakeKind                  = reflect.TypeOf(Fake{}).Name()
 	VaultDynamicSecretKind    = reflect.TypeOf(VaultDynamicSecret{}).Name()
@@ -79,6 +80,7 @@ func init() {
 	SchemeBuilder.Register(&GithubAccessToken{}, &GithubAccessTokenList{})
 	SchemeBuilder.Register(&QuayAccessToken{}, &QuayAccessTokenList{})
 	SchemeBuilder.Register(&Password{}, &PasswordList{})
+	SchemeBuilder.Register(&SSHKey{}, &SSHKeyList{})
 	SchemeBuilder.Register(&STSSessionToken{}, &STSSessionTokenList{})
 	SchemeBuilder.Register(&UUID{}, &UUIDList{})
 	SchemeBuilder.Register(&VaultDynamicSecret{}, &VaultDynamicSecretList{})

+ 3 - 1
apis/generators/v1alpha1/types_cluster.go

@@ -27,7 +27,7 @@ type ClusterGeneratorSpec struct {
 }
 
 // GeneratorKind represents a kind of generator.
-// +kubebuilder:validation:Enum=ACRAccessToken;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana
+// +kubebuilder:validation:Enum=ACRAccessToken;ECRAuthorizationToken;Fake;GCRAccessToken;GithubAccessToken;QuayAccessToken;Password;SSHKey;STSSessionToken;UUID;VaultDynamicSecret;Webhook;Grafana
 type GeneratorKind string
 
 const (
@@ -38,6 +38,7 @@ const (
 	GeneratorKindGithubAccessToken     GeneratorKind = "GithubAccessToken"
 	GeneratorKindQuayAccessToken       GeneratorKind = "QuayAccessToken"
 	GeneratorKindPassword              GeneratorKind = "Password"
+	GeneratorKindSSHKey                GeneratorKind = "SSHKey"
 	GeneratorKindSTSSessionToken       GeneratorKind = "STSSessionToken"
 	GeneratorKindUUID                  GeneratorKind = "UUID"
 	GeneratorKindVaultDynamicSecret    GeneratorKind = "VaultDynamicSecret"
@@ -56,6 +57,7 @@ type GeneratorSpec struct {
 	GithubAccessTokenSpec     *GithubAccessTokenSpec     `json:"githubAccessTokenSpec,omitempty"`
 	QuayAccessTokenSpec       *QuayAccessTokenSpec       `json:"quayAccessTokenSpec,omitempty"`
 	PasswordSpec              *PasswordSpec              `json:"passwordSpec,omitempty"`
+	SSHKeySpec                *SSHKeySpec                `json:"sshKeySpec,omitempty"`
 	STSSessionTokenSpec       *STSSessionTokenSpec       `json:"stsSessionTokenSpec,omitempty"`
 	UUIDSpec                  *UUIDSpec                  `json:"uuidSpec,omitempty"`
 	VaultDynamicSecretSpec    *VaultDynamicSecretSpec    `json:"vaultDynamicSecretSpec,omitempty"`

+ 59 - 0
apis/generators/v1alpha1/types_sshkey.go

@@ -0,0 +1,59 @@
+/*
+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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// SSHKeySpec controls the behavior of the ssh key generator.
+type SSHKeySpec struct {
+	// KeyType specifies the SSH key type (rsa, ed25519)
+	// +kubebuilder:validation:Enum=rsa;ed25519
+	// +kubebuilder:default="rsa"
+	KeyType string `json:"keyType,omitempty"`
+
+	// KeySize specifies the key size for RSA keys (default: 2048)
+	// For RSA keys: 2048, 3072, 4096
+	// Ignored for ed25519 keys
+	// +kubebuilder:validation:Minimum=256
+	// +kubebuilder:validation:Maximum=8192
+	KeySize *int `json:"keySize,omitempty"`
+
+	// Comment specifies an optional comment for the SSH key
+	Comment string `json:"comment,omitempty"`
+}
+
+// SSHKey generates SSH key pairs.
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:metadata:labels="external-secrets.io/component=controller"
+// +kubebuilder:resource:scope=Namespaced,categories={external-secrets, external-secrets-generators}
+type SSHKey struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec SSHKeySpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// SSHKeyList contains a list of SSHKey resources.
+type SSHKeyList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []SSHKey `json:"items"`
+}

+ 83 - 0
apis/generators/v1alpha1/zz_generated.deepcopy.go

@@ -698,6 +698,11 @@ func (in *GeneratorSpec) DeepCopyInto(out *GeneratorSpec) {
 		*out = new(PasswordSpec)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.SSHKeySpec != nil {
+		in, out := &in.SSHKeySpec, &out.SSHKeySpec
+		*out = new(SSHKeySpec)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.STSSessionTokenSpec != nil {
 		in, out := &in.STSSessionTokenSpec, &out.STSSessionTokenSpec
 		*out = new(STSSessionTokenSpec)
@@ -1449,6 +1454,84 @@ func (in *RequestParameters) DeepCopy() *RequestParameters {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SSHKey) DeepCopyInto(out *SSHKey) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSHKey.
+func (in *SSHKey) DeepCopy() *SSHKey {
+	if in == nil {
+		return nil
+	}
+	out := new(SSHKey)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SSHKey) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SSHKeyList) DeepCopyInto(out *SSHKeyList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]SSHKey, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSHKeyList.
+func (in *SSHKeyList) DeepCopy() *SSHKeyList {
+	if in == nil {
+		return nil
+	}
+	out := new(SSHKeyList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SSHKeyList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SSHKeySpec) DeepCopyInto(out *SSHKeySpec) {
+	*out = *in
+	if in.KeySize != nil {
+		in, out := &in.KeySize, &out.KeySize
+		*out = new(int)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSHKeySpec.
+func (in *SSHKeySpec) DeepCopy() *SSHKeySpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SSHKeySpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *STSSessionToken) DeepCopyInto(out *STSSessionToken) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta

+ 4 - 0
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -167,6 +167,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -395,6 +396,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -958,6 +960,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -1157,6 +1160,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret

+ 1 - 0
config/crds/bases/external-secrets.io_clusterpushsecrets.yaml

@@ -269,6 +269,7 @@ spec:
                             - GithubAccessToken
                             - QuayAccessToken
                             - Password
+                            - SSHKey
                             - STSSessionToken
                             - UUID
                             - VaultDynamicSecret

+ 4 - 0
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -147,6 +147,7 @@ spec:
                               - GithubAccessToken
                               - QuayAccessToken
                               - Password
+                              - SSHKey
                               - STSSessionToken
                               - UUID
                               - VaultDynamicSecret
@@ -374,6 +375,7 @@ spec:
                               - GithubAccessToken
                               - QuayAccessToken
                               - Password
+                              - SSHKey
                               - STSSessionToken
                               - UUID
                               - VaultDynamicSecret
@@ -796,6 +798,7 @@ spec:
                               - GithubAccessToken
                               - QuayAccessToken
                               - Password
+                              - SSHKey
                               - STSSessionToken
                               - UUID
                               - VaultDynamicSecret
@@ -994,6 +997,7 @@ spec:
                               - GithubAccessToken
                               - QuayAccessToken
                               - Password
+                              - SSHKey
                               - STSSessionToken
                               - UUID
                               - VaultDynamicSecret

+ 1 - 0
config/crds/bases/external-secrets.io_pushsecrets.yaml

@@ -193,6 +193,7 @@ spec:
                         - GithubAccessToken
                         - QuayAccessToken
                         - Password
+                        - SSHKey
                         - STSSessionToken
                         - UUID
                         - VaultDynamicSecret

+ 24 - 0
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -752,6 +752,29 @@ spec:
                     - robotAccount
                     - serviceAccountRef
                     type: object
+                  sshKeySpec:
+                    description: SSHKeySpec controls the behavior of the ssh key generator.
+                    properties:
+                      comment:
+                        description: Comment specifies an optional comment for the
+                          SSH key
+                        type: string
+                      keySize:
+                        description: |-
+                          KeySize specifies the key size for RSA keys (default: 2048)
+                          For RSA keys: 2048, 3072, 4096
+                          Ignored for ed25519 keys
+                        maximum: 8192
+                        minimum: 256
+                        type: integer
+                      keyType:
+                        default: rsa
+                        description: KeyType specifies the SSH key type (rsa, ed25519)
+                        enum:
+                        - rsa
+                        - ed25519
+                        type: string
+                    type: object
                   stsSessionTokenSpec:
                     properties:
                       auth:
@@ -1953,6 +1976,7 @@ spec:
                 - GithubAccessToken
                 - QuayAccessToken
                 - Password
+                - SSHKey
                 - STSSessionToken
                 - UUID
                 - VaultDynamicSecret

+ 69 - 0
config/crds/bases/generators.external-secrets.io_sshkeys.yaml

@@ -0,0 +1,69 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.18.0
+  labels:
+    external-secrets.io/component: controller
+  name: sshkeys.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+    - external-secrets
+    - external-secrets-generators
+    kind: SSHKey
+    listKind: SSHKeyList
+    plural: sshkeys
+    singular: sshkey
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: SSHKey generates SSH key pairs.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: SSHKeySpec controls the behavior of the ssh key generator.
+            properties:
+              comment:
+                description: Comment specifies an optional comment for the SSH key
+                type: string
+              keySize:
+                description: |-
+                  KeySize specifies the key size for RSA keys (default: 2048)
+                  For RSA keys: 2048, 3072, 4096
+                  Ignored for ed25519 keys
+                maximum: 8192
+                minimum: 256
+                type: integer
+              keyType:
+                default: rsa
+                description: KeyType specifies the SSH key type (rsa, ed25519)
+                enum:
+                - rsa
+                - ed25519
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 1 - 0
config/crds/bases/kustomization.yaml

@@ -19,6 +19,7 @@ resources:
   - generators.external-secrets.io_mfas.yaml
   - generators.external-secrets.io_passwords.yaml
   - generators.external-secrets.io_quayaccesstokens.yaml
+  - generators.external-secrets.io_sshkeys.yaml
   - generators.external-secrets.io_stssessiontokens.yaml
   - generators.external-secrets.io_uuids.yaml
   - generators.external-secrets.io_vaultdynamicsecrets.yaml

+ 3 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -103,6 +103,7 @@ rules:
     - "githubaccesstokens"
     - "quayaccesstokens"
     - "passwords"
+    - "sshkeys"
     - "stssessiontokens"
     - "uuids"
     - "vaultdynamicsecrets"
@@ -224,6 +225,7 @@ rules:
     - "githubaccesstokens"
     - "quayaccesstokens"
     - "passwords"
+    - "sshkeys"
     - "vaultdynamicsecrets"
     - "webhooks"
     - "grafanas"
@@ -284,6 +286,7 @@ rules:
     - "githubaccesstokens"
     - "quayaccesstokens"
     - "passwords"
+    - "sshkeys"
     - "vaultdynamicsecrets"
     - "webhooks"
     - "grafanas"

+ 454 - 0
deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap

@@ -2075,6 +2075,460 @@ should match snapshot of default values:
                                   required:
                                     - identityId
                                   type: object
+                                gcpIamAuthCredentials:
+                                  properties:
+                                    identityId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    serviceAccountKeyFilePath:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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:
+                                    - identityId
+                                    - serviceAccountKeyFilePath
+                                  type: object
+                                gcpIdTokenAuthCredentials:
+                                  properties:
+                                    identityId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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:
+                                    - identityId
+                                  type: object
+                                jwtAuthCredentials:
+                                  properties:
+                                    identityId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    jwt:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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:
+                                    - identityId
+                                    - jwt
+                                  type: object
+                                ldapAuthCredentials:
+                                  properties:
+                                    identityId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    ldapPassword:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    ldapUsername:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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:
+                                    - identityId
+                                    - ldapPassword
+                                    - ldapUsername
+                                  type: object
+                                ociAuthCredentials:
+                                  properties:
+                                    fingerprint:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    identityId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    privateKey:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    privateKeyPassphrase:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    region:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    tenancyId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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
+                                    userId:
+                                      description: |-
+                                        A reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      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:
+                                    - fingerprint
+                                    - identityId
+                                    - privateKey
+                                    - region
+                                    - tenancyId
+                                    - userId
+                                  type: object
                                 universalAuthCredentials:
                                   properties:
                                     clientId:

+ 103 - 0
deploy/crds/bundle.yaml

@@ -157,6 +157,7 @@ spec:
                                       - GithubAccessToken
                                       - QuayAccessToken
                                       - Password
+                                      - SSHKey
                                       - STSSessionToken
                                       - UUID
                                       - VaultDynamicSecret
@@ -373,6 +374,7 @@ spec:
                                       - GithubAccessToken
                                       - QuayAccessToken
                                       - Password
+                                      - SSHKey
                                       - STSSessionToken
                                       - UUID
                                       - VaultDynamicSecret
@@ -904,6 +906,7 @@ spec:
                                       - GithubAccessToken
                                       - QuayAccessToken
                                       - Password
+                                      - SSHKey
                                       - STSSessionToken
                                       - UUID
                                       - VaultDynamicSecret
@@ -1094,6 +1097,7 @@ spec:
                                       - GithubAccessToken
                                       - QuayAccessToken
                                       - Password
+                                      - SSHKey
                                       - STSSessionToken
                                       - UUID
                                       - VaultDynamicSecret
@@ -1743,6 +1747,7 @@ spec:
                                 - GithubAccessToken
                                 - QuayAccessToken
                                 - Password
+                                - SSHKey
                                 - STSSessionToken
                                 - UUID
                                 - VaultDynamicSecret
@@ -11097,6 +11102,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -11313,6 +11319,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -11720,6 +11727,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -11910,6 +11918,7 @@ spec:
                                   - GithubAccessToken
                                   - QuayAccessToken
                                   - Password
+                                  - SSHKey
                                   - STSSessionToken
                                   - UUID
                                   - VaultDynamicSecret
@@ -12384,6 +12393,7 @@ spec:
                             - GithubAccessToken
                             - QuayAccessToken
                             - Password
+                            - SSHKey
                             - STSSessionToken
                             - UUID
                             - VaultDynamicSecret
@@ -22543,6 +22553,28 @@ spec:
                         - robotAccount
                         - serviceAccountRef
                       type: object
+                    sshKeySpec:
+                      description: SSHKeySpec controls the behavior of the ssh key generator.
+                      properties:
+                        comment:
+                          description: Comment specifies an optional comment for the SSH key
+                          type: string
+                        keySize:
+                          description: |-
+                            KeySize specifies the key size for RSA keys (default: 2048)
+                            For RSA keys: 2048, 3072, 4096
+                            Ignored for ed25519 keys
+                          maximum: 8192
+                          minimum: 256
+                          type: integer
+                        keyType:
+                          default: rsa
+                          description: KeyType specifies the SSH key type (rsa, ed25519)
+                          enum:
+                            - rsa
+                            - ed25519
+                          type: string
+                      type: object
                     stsSessionTokenSpec:
                       properties:
                         auth:
@@ -23686,6 +23718,7 @@ spec:
                     - GithubAccessToken
                     - QuayAccessToken
                     - Password
+                    - SSHKey
                     - STSSessionToken
                     - UUID
                     - VaultDynamicSecret
@@ -24720,6 +24753,76 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.18.0
   labels:
     external-secrets.io/component: controller
+  name: sshkeys.generators.external-secrets.io
+spec:
+  group: generators.external-secrets.io
+  names:
+    categories:
+      - external-secrets
+      - external-secrets-generators
+    kind: SSHKey
+    listKind: SSHKeyList
+    plural: sshkeys
+    singular: sshkey
+  scope: Namespaced
+  versions:
+    - name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          description: SSHKey generates SSH key pairs.
+          properties:
+            apiVersion:
+              description: |-
+                APIVersion defines the versioned schema of this representation of an object.
+                Servers should convert recognized schemas to the latest internal value, and
+                may reject unrecognized values.
+                More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+              type: string
+            kind:
+              description: |-
+                Kind is a string value representing the REST resource this object represents.
+                Servers may infer this from the endpoint the client submits requests to.
+                Cannot be updated.
+                In CamelCase.
+                More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: SSHKeySpec controls the behavior of the ssh key generator.
+              properties:
+                comment:
+                  description: Comment specifies an optional comment for the SSH key
+                  type: string
+                keySize:
+                  description: |-
+                    KeySize specifies the key size for RSA keys (default: 2048)
+                    For RSA keys: 2048, 3072, 4096
+                    Ignored for ed25519 keys
+                  maximum: 8192
+                  minimum: 256
+                  type: integer
+                keyType:
+                  default: rsa
+                  description: KeyType specifies the SSH key type (rsa, ed25519)
+                  enum:
+                    - rsa
+                    - ed25519
+                  type: string
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.18.0
+  labels:
+    external-secrets.io/component: controller
   name: stssessiontokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 63 - 0
docs/api/generator/sshkey.md

@@ -0,0 +1,63 @@
+# SSHKey Generator
+
+The SSHKey generator provides SSH key pairs that you can use for authentication in your applications. It supports generating RSA and Ed25519 keys with configurable key sizes and comments.
+
+## Output Keys and Values
+
+| Key        | Description                     |
+| ---------- | ------------------------------- |
+| privateKey | the generated SSH private key   |
+| publicKey  | the generated SSH public key    |
+
+## Parameters
+
+| Parameter | Description                                                        | Default | Required |
+| --------- | ------------------------------------------------------------------ | ------- | -------- |
+| keyType   | SSH key type (rsa, ed25519)                                        | rsa     | No       |
+| keySize   | Key size for RSA keys (2048, 3072, 4096); ignored for ed25519      | 2048    | No       |
+| comment   | Optional comment for the SSH key                                   | ""      | No       |
+
+## Example Manifest
+
+Ed25519 SSH key (recommended):
+
+```yaml
+{% include 'generator-sshkey.yaml' %}
+```
+
+RSA SSH key with custom size:
+
+```yaml
+{% include 'generator-sshkey-rsa.yaml' %}
+```
+
+Example `ExternalSecret` that references the SSHKey generator:
+
+```yaml
+{% include 'generator-sshkey-example.yaml' %}
+```
+
+This will generate a `Kind=Secret` with keys called 'privateKey' and 'publicKey' containing the SSH key pair.
+
+## Supported Key Types
+
+### RSA Keys
+
+- Supports key sizes: 2048, 3072, 4096 bits
+- Default key size: 2048 bits
+- Good compatibility with older systems
+- Can specify custom keySize in the spec
+
+### Ed25519 Keys
+
+- Fixed key size (keySize parameter ignored if specified)
+- Modern, secure, and efficient
+- Recommended for new deployments
+- Effective key size is always 256 bits (equivalent security to 3072-bit RSA)
+
+## Security Considerations
+
+- Generated keys are cryptographically secure using Go's crypto/rand
+- Private keys are stored in OpenSSH format
+- Keys are generated fresh on each reconciliation unless cached
+- Consider key rotation policies for production use

+ 1 - 0
docs/guides/generator.md

@@ -61,6 +61,7 @@ type GeneratorSpec struct {
 	GCRAccessTokenSpec        *GCRAccessTokenSpec        `json:"gcrAccessTokenSpec,omitempty"`
 	GithubAccessTokenSpec     *GithubAccessTokenSpec     `json:"githubAccessTokenSpec,omitempty"`
 	PasswordSpec              *PasswordSpec              `json:"passwordSpec,omitempty"`
+	SSHKeySpec                *SSHKeySpec                `json:"sshKeySpec,omitempty"`
 	STSSessionTokenSpec       *STSSessionTokenSpec       `json:"stsSessionTokenSpec,omitempty"`
 	UUIDSpec                  *UUIDSpec                  `json:"uuidSpec,omitempty"`
 	VaultDynamicSecretSpec    *VaultDynamicSecretSpec    `json:"vaultDynamicSecretSpec,omitempty"`

+ 14 - 0
docs/snippets/generator-sshkey-example.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: example-ssh-key
+spec:
+  refreshInterval: "30m"
+  target:
+    name: ssh-key-secret
+  dataFrom:
+    - sourceRef:
+        generatorRef:
+          apiVersion: generators.external-secrets.io/v1alpha1
+          kind: SSHKey
+          name: example-ssh-key

+ 14 - 0
docs/snippets/generator-sshkey-rsa-example.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: example-rsa-key
+spec:
+  refreshInterval: "30m"
+  target:
+    name: ssh-rsa-secret
+  dataFrom:
+    - sourceRef:
+        generatorRef:
+          apiVersion: generators.external-secrets.io/v1alpha1
+          kind: SSHKey
+          name: example-rsa-key

+ 8 - 0
docs/snippets/generator-sshkey-rsa.yaml

@@ -0,0 +1,8 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: SSHKey
+metadata:
+  name: example-rsa-key
+spec:
+  keyType: "rsa"
+  keySize: 4096
+  comment: "rsa@example.com"

+ 7 - 0
docs/snippets/generator-sshkey.yaml

@@ -0,0 +1,7 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: SSHKey
+metadata:
+  name: example-ssh-key
+spec:
+  keyType: "ed25519"
+  comment: "user@example.com"

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

@@ -26,6 +26,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/generator/mfa"
 	_ "github.com/external-secrets/external-secrets/pkg/generator/password"
 	_ "github.com/external-secrets/external-secrets/pkg/generator/quay"
+	_ "github.com/external-secrets/external-secrets/pkg/generator/sshkey"
 	_ "github.com/external-secrets/external-secrets/pkg/generator/sts"
 	_ "github.com/external-secrets/external-secrets/pkg/generator/uuid"
 	_ "github.com/external-secrets/external-secrets/pkg/generator/vault"

+ 166 - 0
pkg/generator/sshkey/sshkey.go

@@ -0,0 +1,166 @@
+/*
+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 sshkey
+
+import (
+	"context"
+	"crypto/ed25519"
+	"crypto/rand"
+	"crypto/rsa"
+	"encoding/pem"
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/ssh"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/yaml"
+
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+)
+
+type Generator struct{}
+
+const (
+	defaultKeyType = "rsa"
+	defaultKeySize = 2048
+
+	errNoSpec      = "no config spec provided"
+	errParseSpec   = "unable to parse spec: %w"
+	errGenerateKey = "unable to generate SSH key: %w"
+	errUnsupported = "unsupported key type: %s"
+)
+
+type generateFunc func(keyType string, keySize *int, comment string) (privateKey, publicKey []byte, err error)
+
+func (g *Generator) Generate(_ context.Context, jsonSpec *apiextensions.JSON, _ client.Client, _ string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
+	return g.generate(
+		jsonSpec,
+		generateSSHKey,
+	)
+}
+
+func (g *Generator) Cleanup(_ context.Context, jsonSpec *apiextensions.JSON, state genv1alpha1.GeneratorProviderState, _ client.Client, _ string) error {
+	return nil
+}
+
+func (g *Generator) generate(jsonSpec *apiextensions.JSON, keyGen generateFunc) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
+	if jsonSpec == nil {
+		return nil, nil, errors.New(errNoSpec)
+	}
+
+	res, err := parseSpec(jsonSpec.Raw)
+	if err != nil {
+		return nil, nil, fmt.Errorf(errParseSpec, err)
+	}
+
+	keyType := defaultKeyType
+	if res.Spec.KeyType != "" {
+		keyType = res.Spec.KeyType
+	}
+
+	privateKey, publicKey, err := keyGen(keyType, res.Spec.KeySize, res.Spec.Comment)
+	if err != nil {
+		return nil, nil, fmt.Errorf(errGenerateKey, err)
+	}
+
+	return map[string][]byte{
+		"privateKey": privateKey,
+		"publicKey":  publicKey,
+	}, nil, nil
+}
+
+func generateSSHKey(keyType string, keySize *int, comment string) (privateKey, publicKey []byte, err error) {
+	switch keyType {
+	case "rsa":
+		bits := 2048
+		if keySize != nil {
+			bits = *keySize
+		}
+		return generateRSAKey(bits, comment)
+	case "ed25519":
+		return generateEd25519Key(comment)
+	default:
+		return nil, nil, fmt.Errorf(errUnsupported, keyType)
+	}
+}
+
+func generateRSAKey(keySize int, comment string) (privateKey, publicKey []byte, err error) {
+	// Generate RSA private key
+	rsaKey, err := rsa.GenerateKey(rand.Reader, keySize)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH private key in OpenSSH format
+	sshPrivateKey, err := ssh.MarshalPrivateKey(rsaKey, comment)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH public key
+	sshPublicKey, err := ssh.NewPublicKey(&rsaKey.PublicKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
+	if comment != "" {
+		// Remove the newline and add comment
+		publicKeyStr := string(publicKeyBytes[:len(publicKeyBytes)-1]) + " " + comment + "\n"
+		publicKeyBytes = []byte(publicKeyStr)
+	}
+
+	return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
+}
+
+func generateEd25519Key(comment string) (privateKey, publicKey []byte, err error) {
+	// Generate Ed25519 private key
+	_, ed25519PrivateKey, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH private key in OpenSSH format
+	sshPrivateKey, err := ssh.MarshalPrivateKey(ed25519PrivateKey, comment)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH public key
+	sshPublicKey, err := ssh.NewPublicKey(ed25519PrivateKey.Public())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
+	if comment != "" {
+		// Remove the newline and add comment
+		publicKeyStr := string(publicKeyBytes[:len(publicKeyBytes)-1]) + " " + comment + "\n"
+		publicKeyBytes = []byte(publicKeyStr)
+	}
+
+	return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
+}
+
+func parseSpec(data []byte) (*genv1alpha1.SSHKey, error) {
+	var spec genv1alpha1.SSHKey
+	err := yaml.Unmarshal(data, &spec)
+	return &spec, err
+}
+
+func init() {
+	genv1alpha1.Register(genv1alpha1.SSHKeyKind, &Generator{})
+}

+ 184 - 0
pkg/generator/sshkey/sshkey_test.go

@@ -0,0 +1,184 @@
+/*
+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 sshkey
+
+import (
+	"context"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
+	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+)
+
+func TestGenerate(t *testing.T) {
+	g := &Generator{}
+
+	tests := []struct {
+		name        string
+		jsonSpec    *apiextensions.JSON
+		wantErr     bool
+		expectedErr string
+		validate    func(t *testing.T, result map[string][]byte)
+	}{
+		{
+			name:        "nil spec should return error",
+			jsonSpec:    nil,
+			wantErr:     true,
+			expectedErr: errNoSpec,
+		},
+		{
+			name:     "empty spec should use defaults",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, len(result["privateKey"]) > 0)
+				assert.True(t, len(result["publicKey"]) > 0)
+				// Should contain RSA private key header
+				assert.Contains(t, string(result["privateKey"]), "BEGIN OPENSSH PRIVATE KEY")
+				// Should contain ssh-rsa public key
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ssh-rsa "))
+			},
+		},
+		{
+			name:     "rsa key with custom size",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"rsa","keySize":4096}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, len(result["privateKey"]) > 0)
+				assert.True(t, len(result["publicKey"]) > 0)
+			},
+		},
+		{
+			name:     "ed25519 key",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"ed25519"}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, len(result["privateKey"]) > 0)
+				assert.True(t, len(result["publicKey"]) > 0)
+				// Should contain ed25519 public key
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ssh-ed25519 "))
+			},
+		},
+		{
+			name:     "ed25519 key with explicit keySize (should be ignored)",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"ed25519","keySize":4096}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, len(result["privateKey"]) > 0)
+				assert.True(t, len(result["publicKey"]) > 0)
+				// Should contain ed25519 public key (keySize should be ignored)
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ssh-ed25519 "))
+			},
+		},
+		{
+			name:     "key with comment",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"rsa","comment":"test@example.com"}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				// Should contain the comment in public key
+				assert.Contains(t, string(result["publicKey"]), "test@example.com")
+			},
+		},
+		{
+			name:        "unsupported key type",
+			jsonSpec:    &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"unsupported"}}`)},
+			wantErr:     true,
+			expectedErr: "unsupported key type",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result, _, err := g.Generate(context.Background(), tt.jsonSpec, nil, "")
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				if tt.expectedErr != "" {
+					assert.Contains(t, err.Error(), tt.expectedErr)
+				}
+				return
+			}
+
+			assert.NoError(t, err)
+			if tt.validate != nil {
+				tt.validate(t, result)
+			}
+		})
+	}
+}
+
+func TestCleanup(t *testing.T) {
+	g := &Generator{}
+	err := g.Cleanup(context.Background(), nil, nil, nil, "")
+	assert.NoError(t, err)
+}
+
+func TestParseSpec(t *testing.T) {
+	tests := []struct {
+		name     string
+		data     []byte
+		expected *genv1alpha1.SSHKey
+		wantErr  bool
+	}{
+		{
+			name: "valid spec",
+			data: []byte(`{"spec":{"keyType":"rsa","keySize":2048,"comment":"test"}}`),
+			expected: &genv1alpha1.SSHKey{
+				Spec: genv1alpha1.SSHKeySpec{
+					KeyType: "rsa",
+					KeySize: func() *int { i := 2048; return &i }(),
+					Comment: "test",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "empty spec",
+			data:    []byte(`{"spec":{}}`),
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result, err := parseSpec(tt.data)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+			if tt.expected != nil {
+				assert.Equal(t, tt.expected.Spec.KeyType, result.Spec.KeyType)
+				assert.Equal(t, tt.expected.Spec.KeySize, result.Spec.KeySize)
+				assert.Equal(t, tt.expected.Spec.Comment, result.Spec.Comment)
+			}
+		})
+	}
+}

+ 11 - 0
pkg/utils/resolvers/generator.go

@@ -212,6 +212,17 @@ func clusterGeneratorToVirtual(gen *genv1alpha1.ClusterGenerator) (client.Object
 			},
 			Spec: *gen.Spec.Generator.PasswordSpec,
 		}, nil
+	case genv1alpha1.GeneratorKindSSHKey:
+		if gen.Spec.Generator.SSHKeySpec == nil {
+			return nil, fmt.Errorf("when kind is %s, SSHKeySpec must be set", gen.Spec.Kind)
+		}
+		return &genv1alpha1.SSHKey{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: genv1alpha1.SchemeGroupVersion.String(),
+				Kind:       genv1alpha1.SSHKeyKind,
+			},
+			Spec: *gen.Spec.Generator.SSHKeySpec,
+		}, nil
 	case genv1alpha1.GeneratorKindSTSSessionToken:
 		if gen.Spec.Generator.STSSessionTokenSpec == nil {
 			return nil, fmt.Errorf("when kind is %s, STSSessionTokenSpec must be set", gen.Spec.Kind)