Browse Source

feat: add support for ECDSA ssh keys (#5559)

Signed-off-by: Craig Fielder <craig.fielder@teslagovernment.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Craig James Fielder 5 months ago
parent
commit
d39c6c0720

+ 4 - 3
apis/generators/v1alpha1/types_sshkey.go

@@ -22,13 +22,14 @@ import (
 
 
 // SSHKeySpec controls the behavior of the ssh key generator.
 // SSHKeySpec controls the behavior of the ssh key generator.
 type SSHKeySpec struct {
 type SSHKeySpec struct {
-	// KeyType specifies the SSH key type (rsa, ed25519)
-	// +kubebuilder:validation:Enum=rsa;ed25519
+	// KeyType specifies the SSH key type (rsa, ecdsa, ed25519)
+	// +kubebuilder:validation:Enum=rsa;ecdsa;ed25519
 	// +kubebuilder:default="rsa"
 	// +kubebuilder:default="rsa"
 	KeyType string `json:"keyType,omitempty"`
 	KeyType string `json:"keyType,omitempty"`
 
 
-	// KeySize specifies the key size for RSA keys (default: 2048)
+	// KeySize specifies the key size for RSA keys (default: 2048) and ECDSA keys (default: 256).
 	// For RSA keys: 2048, 3072, 4096
 	// For RSA keys: 2048, 3072, 4096
+	// For ECDSA keys: 256, 384, 521
 	// Ignored for ed25519 keys
 	// Ignored for ed25519 keys
 	// +kubebuilder:validation:Minimum=256
 	// +kubebuilder:validation:Minimum=256
 	// +kubebuilder:validation:Maximum=8192
 	// +kubebuilder:validation:Maximum=8192

+ 5 - 2
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -969,17 +969,20 @@ spec:
                         type: string
                         type: string
                       keySize:
                       keySize:
                         description: |-
                         description: |-
-                          KeySize specifies the key size for RSA keys (default: 2048)
+                          KeySize specifies the key size for RSA keys (default: 2048) and ECDSA keys (default: 256).
                           For RSA keys: 2048, 3072, 4096
                           For RSA keys: 2048, 3072, 4096
+                          For ECDSA keys: 256, 384, 521
                           Ignored for ed25519 keys
                           Ignored for ed25519 keys
                         maximum: 8192
                         maximum: 8192
                         minimum: 256
                         minimum: 256
                         type: integer
                         type: integer
                       keyType:
                       keyType:
                         default: rsa
                         default: rsa
-                        description: KeyType specifies the SSH key type (rsa, ed25519)
+                        description: KeyType specifies the SSH key type (rsa, ecdsa,
+                          ed25519)
                         enum:
                         enum:
                         - rsa
                         - rsa
+                        - ecdsa
                         - ed25519
                         - ed25519
                         type: string
                         type: string
                     type: object
                     type: object

+ 4 - 2
config/crds/bases/generators.external-secrets.io_sshkeys.yaml

@@ -48,17 +48,19 @@ spec:
                 type: string
                 type: string
               keySize:
               keySize:
                 description: |-
                 description: |-
-                  KeySize specifies the key size for RSA keys (default: 2048)
+                  KeySize specifies the key size for RSA keys (default: 2048) and ECDSA keys (default: 256).
                   For RSA keys: 2048, 3072, 4096
                   For RSA keys: 2048, 3072, 4096
+                  For ECDSA keys: 256, 384, 521
                   Ignored for ed25519 keys
                   Ignored for ed25519 keys
                 maximum: 8192
                 maximum: 8192
                 minimum: 256
                 minimum: 256
                 type: integer
                 type: integer
               keyType:
               keyType:
                 default: rsa
                 default: rsa
-                description: KeyType specifies the SSH key type (rsa, ed25519)
+                description: KeyType specifies the SSH key type (rsa, ecdsa, ed25519)
                 enum:
                 enum:
                 - rsa
                 - rsa
+                - ecdsa
                 - ed25519
                 - ed25519
                 type: string
                 type: string
             type: object
             type: object

+ 8 - 4
deploy/crds/bundle.yaml

@@ -24108,17 +24108,19 @@ spec:
                           type: string
                           type: string
                         keySize:
                         keySize:
                           description: |-
                           description: |-
-                            KeySize specifies the key size for RSA keys (default: 2048)
+                            KeySize specifies the key size for RSA keys (default: 2048) and ECDSA keys (default: 256).
                             For RSA keys: 2048, 3072, 4096
                             For RSA keys: 2048, 3072, 4096
+                            For ECDSA keys: 256, 384, 521
                             Ignored for ed25519 keys
                             Ignored for ed25519 keys
                           maximum: 8192
                           maximum: 8192
                           minimum: 256
                           minimum: 256
                           type: integer
                           type: integer
                         keyType:
                         keyType:
                           default: rsa
                           default: rsa
-                          description: KeyType specifies the SSH key type (rsa, ed25519)
+                          description: KeyType specifies the SSH key type (rsa, ecdsa, ed25519)
                           enum:
                           enum:
                             - rsa
                             - rsa
+                            - ecdsa
                             - ed25519
                             - ed25519
                           type: string
                           type: string
                       type: object
                       type: object
@@ -26507,17 +26509,19 @@ spec:
                   type: string
                   type: string
                 keySize:
                 keySize:
                   description: |-
                   description: |-
-                    KeySize specifies the key size for RSA keys (default: 2048)
+                    KeySize specifies the key size for RSA keys (default: 2048) and ECDSA keys (default: 256).
                     For RSA keys: 2048, 3072, 4096
                     For RSA keys: 2048, 3072, 4096
+                    For ECDSA keys: 256, 384, 521
                     Ignored for ed25519 keys
                     Ignored for ed25519 keys
                   maximum: 8192
                   maximum: 8192
                   minimum: 256
                   minimum: 256
                   type: integer
                   type: integer
                 keyType:
                 keyType:
                   default: rsa
                   default: rsa
-                  description: KeyType specifies the SSH key type (rsa, ed25519)
+                  description: KeyType specifies the SSH key type (rsa, ecdsa, ed25519)
                   enum:
                   enum:
                     - rsa
                     - rsa
+                    - ecdsa
                     - ed25519
                     - ed25519
                   type: string
                   type: string
               type: object
               type: object

+ 14 - 2
docs/api/generator/sshkey.md

@@ -13,8 +13,8 @@ The SSHKey generator provides SSH key pairs that you can use for authentication
 
 
 | Parameter | Description                                                        | Default | Required |
 | 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       |
+| keyType   | SSH key type (rsa, ecdsa, ed25519)                                        | rsa     | No       |
+| keySize   | Key size for RSA keys (2048, 3072, 4096) and ECDSA (256, 384, 521); ignored for ed25519      | 2048 / 256    | No       |
 | comment   | Optional comment for the SSH key                                   | ""      | No       |
 | comment   | Optional comment for the SSH key                                   | ""      | No       |
 
 
 ## Example Manifest
 ## Example Manifest
@@ -31,6 +31,12 @@ RSA SSH key with custom size:
 {% include 'generator-sshkey-rsa.yaml' %}
 {% include 'generator-sshkey-rsa.yaml' %}
 ```
 ```
 
 
+ECDSA SSH key:
+
+```yaml
+{% include 'generator-sshkey-ecdsa.yaml' %}
+```
+
 Example `ExternalSecret` that references the SSHKey generator:
 Example `ExternalSecret` that references the SSHKey generator:
 
 
 ```yaml
 ```yaml
@@ -48,6 +54,12 @@ This will generate a `Kind=Secret` with keys called 'privateKey' and 'publicKey'
 - Good compatibility with older systems
 - Good compatibility with older systems
 - Can specify custom keySize in the spec
 - Can specify custom keySize in the spec
 
 
+### ECDSA Keys
+
+- Supports key sizes: 256, 384, 521 bits
+- Default key size: 256 bits
+- For use in regulated environments
+
 ### Ed25519 Keys
 ### Ed25519 Keys
 
 
 - Fixed key size (keySize parameter ignored if specified)
 - Fixed key size (keySize parameter ignored if specified)

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

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

+ 50 - 1
generators/v1/sshkey/sshkey.go

@@ -19,7 +19,9 @@ package sshkey
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/ecdsa"
 	"crypto/ed25519"
 	"crypto/ed25519"
+	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/rsa"
 	"encoding/pem"
 	"encoding/pem"
@@ -98,6 +100,12 @@ func generateSSHKey(keyType string, keySize *int, comment string) (privateKey, p
 		return generateRSAKey(bits, comment)
 		return generateRSAKey(bits, comment)
 	case "ed25519":
 	case "ed25519":
 		return generateEd25519Key(comment)
 		return generateEd25519Key(comment)
+	case "ecdsa":
+		bits := 256
+		if keySize != nil {
+			bits = *keySize
+		}
+		return generateECDSAKey(bits, comment)
 	default:
 	default:
 		return nil, nil, fmt.Errorf(errUnsupported, keyType)
 		return nil, nil, fmt.Errorf(errUnsupported, keyType)
 	}
 	}
@@ -161,13 +169,54 @@ func generateEd25519Key(comment string) (privateKey, publicKey []byte, err error
 	return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
 	return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
 }
 }
 
 
+func generateECDSAKey(keySize int, comment string) (privateKey, publicKey []byte, err error) {
+	var ellipticCurve elliptic.Curve
+
+	// Select the elliptic curve based on keySize
+	switch keySize {
+	case 256:
+		ellipticCurve = elliptic.P256()
+	case 384:
+		ellipticCurve = elliptic.P384()
+	case 521:
+		ellipticCurve = elliptic.P521()
+	default:
+		ellipticCurve = elliptic.P256()
+	}
+
+	ecdsaKey, err := ecdsa.GenerateKey(ellipticCurve, rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH private key in OpenSSH format
+	sshPrivateKey, err := ssh.MarshalPrivateKey(ecdsaKey, comment)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Create SSH public key
+	sshPublicKey, err := ssh.NewPublicKey(&ecdsaKey.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 parseSpec(data []byte) (*genv1alpha1.SSHKey, error) {
 func parseSpec(data []byte) (*genv1alpha1.SSHKey, error) {
 	var spec genv1alpha1.SSHKey
 	var spec genv1alpha1.SSHKey
 	err := yaml.Unmarshal(data, &spec)
 	err := yaml.Unmarshal(data, &spec)
 	return &spec, err
 	return &spec, err
 }
 }
 
 
-
 // NewGenerator creates a new Generator instance.
 // NewGenerator creates a new Generator instance.
 func NewGenerator() genv1alpha1.Generator {
 func NewGenerator() genv1alpha1.Generator {
 	return &Generator{}
 	return &Generator{}

+ 33 - 0
generators/v1/sshkey/sshkey_test.go

@@ -96,6 +96,39 @@ func TestGenerate(t *testing.T) {
 			},
 			},
 		},
 		},
 		{
 		{
+			name:     "ecdsa key",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"ecdsa"}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ecdsa-sha2-"))
+			},
+		},
+		{
+			name:     "ecdsa key with comment",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"ecdsa","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")
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ecdsa-sha2-"))
+				assert.Contains(t, string(result["publicKey"]), "test@example.com")
+			},
+		},
+		{
+			name:     "ecdsa key with bits specified",
+			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"ecdsa","keySize":521}}`)},
+			wantErr:  false,
+			validate: func(t *testing.T, result map[string][]byte) {
+				assert.Contains(t, result, "privateKey")
+				assert.Contains(t, result, "publicKey")
+				assert.True(t, strings.HasPrefix(string(result["publicKey"]), "ecdsa-sha2-"))
+				assert.True(t, len(result["privateKey"]) > 0)
+				assert.True(t, len(result["publicKey"]) > 0)
+			},
+		},
+		{
 			name:     "key with comment",
 			name:     "key with comment",
 			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"rsa","comment":"test@example.com"}}`)},
 			jsonSpec: &apiextensions.JSON{Raw: []byte(`{"spec":{"keyType":"rsa","comment":"test@example.com"}}`)},
 			wantErr:  false,
 			wantErr:  false,