GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keygen.go 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package ssh
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "os/exec"
  8. "path"
  9. "regexp"
  10. "strconv"
  11. "github.com/spf13/pflag"
  12. )
  13. // OptionalValue is an extension of pflag.Value that remembers if it has been
  14. // set.
  15. type OptionalValue interface {
  16. pflag.Value
  17. Specified() bool
  18. }
  19. // KeyBitsValue is an OptionalValue allowing specification of the -b argument
  20. // to ssh-keygen.
  21. type KeyBitsValue struct {
  22. specified bool
  23. keyBits uint64
  24. }
  25. func (kbv *KeyBitsValue) String() string {
  26. return strconv.FormatUint(kbv.keyBits, 10)
  27. }
  28. func (kbv *KeyBitsValue) Set(s string) error {
  29. v, err := strconv.ParseUint(s, 0, 64)
  30. if err != nil {
  31. return err
  32. }
  33. kbv.keyBits = v
  34. kbv.specified = true
  35. return nil
  36. }
  37. func (kbv *KeyBitsValue) Type() string {
  38. return "uint64"
  39. }
  40. func (kbv *KeyBitsValue) Specified() bool {
  41. return kbv.specified
  42. }
  43. // KeyTypeValue is an OptionalValue allowing specification of the -t argument
  44. // to ssh-keygen.
  45. type KeyTypeValue struct {
  46. specified bool
  47. keyType string
  48. }
  49. func (ktv *KeyTypeValue) String() string {
  50. return ktv.keyType
  51. }
  52. func (ktv *KeyTypeValue) Set(s string) error {
  53. if len(s) > 0 {
  54. ktv.keyType = s
  55. ktv.specified = true
  56. }
  57. return nil
  58. }
  59. func (ktv *KeyTypeValue) Type() string {
  60. return "string"
  61. }
  62. func (ktv *KeyTypeValue) Specified() bool {
  63. return ktv.specified
  64. }
  65. // KeyGen generates a new keypair with ssh-keygen, optionally overriding the
  66. // default type and size. Each generated keypair is written to a new unique
  67. // subdirectory of tmpfsPath, which should point to a tmpfs mount as the
  68. // private key is not encrypted.
  69. func KeyGen(keyBits, keyType OptionalValue, tmpfsPath string) (privateKeyPath string, privateKey []byte, publicKey PublicKey, err error) {
  70. tempDir, err := ioutil.TempDir(tmpfsPath, "..weave-keygen")
  71. if err != nil {
  72. return "", nil, PublicKey{}, err
  73. }
  74. privateKeyPath = path.Join(tempDir, "identity")
  75. args := []string{"-q", "-N", "", "-f", privateKeyPath}
  76. if keyBits.Specified() {
  77. args = append(args, "-b", keyBits.String())
  78. }
  79. if keyType.Specified() {
  80. args = append(args, "-t", keyType.String())
  81. }
  82. cmd := exec.Command("ssh-keygen", args...)
  83. if err := cmd.Run(); err != nil {
  84. return "", nil, PublicKey{}, err
  85. }
  86. privateKey, err = ioutil.ReadFile(privateKeyPath)
  87. if err != nil {
  88. return "", nil, PublicKey{}, err
  89. }
  90. publicKey, err = ExtractPublicKey(privateKeyPath)
  91. if err != nil {
  92. return "", nil, PublicKey{}, err
  93. }
  94. return privateKeyPath, privateKey, publicKey, nil
  95. }
  96. type Fingerprint struct {
  97. Hash string `json:"hash"`
  98. Randomart string `json:"randomart"`
  99. }
  100. var (
  101. fieldRegexp = regexp.MustCompile(`^([[:digit:]]+) ([^:]+):([^ ]+) (.*?) \(([^)]+)\)$`)
  102. captureCount = 6
  103. )
  104. // Fingerprint extracts and returns the hash and randomart of the public key
  105. // associated with the specified private key.
  106. func ExtractFingerprint(privateKeyPath, hashAlgo string) (Fingerprint, error) {
  107. output, err := exec.Command("ssh-keygen", "-l", "-v", "-E", hashAlgo, "-f", privateKeyPath).Output()
  108. if err != nil {
  109. return Fingerprint{}, err
  110. }
  111. i := bytes.IndexByte(output, '\n')
  112. if i == -1 {
  113. return Fingerprint{}, fmt.Errorf("could not parse fingerprint")
  114. }
  115. fields := fieldRegexp.FindSubmatch(output[:i])
  116. if len(fields) != captureCount {
  117. return Fingerprint{}, fmt.Errorf("could not parse fingerprint")
  118. }
  119. return Fingerprint{
  120. Hash: string(fields[3]),
  121. Randomart: string(output[i+1:]),
  122. }, nil
  123. }
  124. type PublicKey struct {
  125. Key string `json:"key"`
  126. Fingerprints map[string]Fingerprint `json:"fingerprints"`
  127. }
  128. // ExtractPublicKey extracts and returns the public key from the specified
  129. // private key, along with its fingerprint hashes.
  130. func ExtractPublicKey(privateKeyPath string) (PublicKey, error) {
  131. keyBytes, err := exec.Command("ssh-keygen", "-y", "-f", privateKeyPath).CombinedOutput()
  132. if err != nil {
  133. return PublicKey{}, errors.New(string(keyBytes))
  134. }
  135. md5Print, err := ExtractFingerprint(privateKeyPath, "md5")
  136. if err != nil {
  137. return PublicKey{}, err
  138. }
  139. sha256Print, err := ExtractFingerprint(privateKeyPath, "sha256")
  140. if err != nil {
  141. return PublicKey{}, err
  142. }
  143. return PublicKey{
  144. Key: string(keyBytes),
  145. Fingerprints: map[string]Fingerprint{
  146. "md5": md5Print,
  147. "sha256": sha256Print,
  148. },
  149. }, nil
  150. }