sshkey.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package sshkey provides functionality for generating SSH key pairs.
  14. package sshkey
  15. import (
  16. "context"
  17. "crypto/ecdsa"
  18. "crypto/ed25519"
  19. "crypto/elliptic"
  20. "crypto/rand"
  21. "crypto/rsa"
  22. "encoding/pem"
  23. "errors"
  24. "fmt"
  25. "golang.org/x/crypto/ssh"
  26. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  27. "sigs.k8s.io/controller-runtime/pkg/client"
  28. "sigs.k8s.io/yaml"
  29. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  30. )
  31. // Generator implements SSH key pair generation functionality.
  32. type Generator struct{}
  33. const (
  34. defaultKeyType = "rsa"
  35. defaultKeySize = 2048
  36. errNoSpec = "no config spec provided"
  37. errParseSpec = "unable to parse spec: %w"
  38. errGenerateKey = "unable to generate SSH key: %w"
  39. errUnsupported = "unsupported key type: %s"
  40. )
  41. type generateFunc func(keyType string, keySize *int, comment string) (privateKey, publicKey []byte, err error)
  42. // Generate creates a new SSH key pair.
  43. func (g *Generator) Generate(_ context.Context, jsonSpec *apiextensions.JSON, _ client.Client, _ string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  44. return g.generate(
  45. jsonSpec,
  46. generateSSHKey,
  47. )
  48. }
  49. // Cleanup performs any necessary cleanup after key generation.
  50. func (g *Generator) Cleanup(_ context.Context, _ *apiextensions.JSON, _ genv1alpha1.GeneratorProviderState, _ client.Client, _ string) error {
  51. return nil
  52. }
  53. func (g *Generator) generate(jsonSpec *apiextensions.JSON, keyGen generateFunc) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  54. if jsonSpec == nil {
  55. return nil, nil, errors.New(errNoSpec)
  56. }
  57. res, err := parseSpec(jsonSpec.Raw)
  58. if err != nil {
  59. return nil, nil, fmt.Errorf(errParseSpec, err)
  60. }
  61. keyType := defaultKeyType
  62. if res.Spec.KeyType != "" {
  63. keyType = res.Spec.KeyType
  64. }
  65. privateKey, publicKey, err := keyGen(keyType, res.Spec.KeySize, res.Spec.Comment)
  66. if err != nil {
  67. return nil, nil, fmt.Errorf(errGenerateKey, err)
  68. }
  69. return map[string][]byte{
  70. "privateKey": privateKey,
  71. "publicKey": publicKey,
  72. }, nil, nil
  73. }
  74. func generateSSHKey(keyType string, keySize *int, comment string) (privateKey, publicKey []byte, err error) {
  75. switch keyType {
  76. case "rsa":
  77. bits := 2048
  78. if keySize != nil {
  79. bits = *keySize
  80. }
  81. return generateRSAKey(bits, comment)
  82. case "ed25519":
  83. return generateEd25519Key(comment)
  84. case "ecdsa":
  85. bits := 256
  86. if keySize != nil {
  87. bits = *keySize
  88. }
  89. return generateECDSAKey(bits, comment)
  90. default:
  91. return nil, nil, fmt.Errorf(errUnsupported, keyType)
  92. }
  93. }
  94. func generateRSAKey(keySize int, comment string) (privateKey, publicKey []byte, err error) {
  95. // Generate RSA private key
  96. rsaKey, err := rsa.GenerateKey(rand.Reader, keySize)
  97. if err != nil {
  98. return nil, nil, err
  99. }
  100. // Create SSH private key in OpenSSH format
  101. sshPrivateKey, err := ssh.MarshalPrivateKey(rsaKey, comment)
  102. if err != nil {
  103. return nil, nil, err
  104. }
  105. // Create SSH public key
  106. sshPublicKey, err := ssh.NewPublicKey(&rsaKey.PublicKey)
  107. if err != nil {
  108. return nil, nil, err
  109. }
  110. publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
  111. if comment != "" {
  112. // Remove the newline and add comment
  113. publicKeyStr := string(publicKeyBytes[:len(publicKeyBytes)-1]) + " " + comment + "\n"
  114. publicKeyBytes = []byte(publicKeyStr)
  115. }
  116. return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
  117. }
  118. func generateEd25519Key(comment string) (privateKey, publicKey []byte, err error) {
  119. // Generate Ed25519 private key
  120. _, ed25519PrivateKey, err := ed25519.GenerateKey(rand.Reader)
  121. if err != nil {
  122. return nil, nil, err
  123. }
  124. // Create SSH private key in OpenSSH format
  125. sshPrivateKey, err := ssh.MarshalPrivateKey(ed25519PrivateKey, comment)
  126. if err != nil {
  127. return nil, nil, err
  128. }
  129. // Create SSH public key
  130. sshPublicKey, err := ssh.NewPublicKey(ed25519PrivateKey.Public())
  131. if err != nil {
  132. return nil, nil, err
  133. }
  134. publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
  135. if comment != "" {
  136. // Remove the newline and add comment
  137. publicKeyStr := string(publicKeyBytes[:len(publicKeyBytes)-1]) + " " + comment + "\n"
  138. publicKeyBytes = []byte(publicKeyStr)
  139. }
  140. return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
  141. }
  142. func generateECDSAKey(keySize int, comment string) (privateKey, publicKey []byte, err error) {
  143. var ellipticCurve elliptic.Curve
  144. // Select the elliptic curve based on keySize
  145. switch keySize {
  146. case 256:
  147. ellipticCurve = elliptic.P256()
  148. case 384:
  149. ellipticCurve = elliptic.P384()
  150. case 521:
  151. ellipticCurve = elliptic.P521()
  152. default:
  153. ellipticCurve = elliptic.P256()
  154. }
  155. ecdsaKey, err := ecdsa.GenerateKey(ellipticCurve, rand.Reader)
  156. if err != nil {
  157. return nil, nil, err
  158. }
  159. // Create SSH private key in OpenSSH format
  160. sshPrivateKey, err := ssh.MarshalPrivateKey(ecdsaKey, comment)
  161. if err != nil {
  162. return nil, nil, err
  163. }
  164. // Create SSH public key
  165. sshPublicKey, err := ssh.NewPublicKey(&ecdsaKey.PublicKey)
  166. if err != nil {
  167. return nil, nil, err
  168. }
  169. publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
  170. if comment != "" {
  171. // Remove the newline and add comment
  172. publicKeyStr := string(publicKeyBytes[:len(publicKeyBytes)-1]) + " " + comment + "\n"
  173. publicKeyBytes = []byte(publicKeyStr)
  174. }
  175. return pem.EncodeToMemory(sshPrivateKey), publicKeyBytes, nil
  176. }
  177. func parseSpec(data []byte) (*genv1alpha1.SSHKey, error) {
  178. var spec genv1alpha1.SSHKey
  179. err := yaml.Unmarshal(data, &spec)
  180. return &spec, err
  181. }
  182. // NewGenerator creates a new Generator instance.
  183. func NewGenerator() genv1alpha1.Generator {
  184. return &Generator{}
  185. }
  186. // Kind returns the generator kind.
  187. func Kind() string {
  188. return string(genv1alpha1.GeneratorKindSSHKey)
  189. }