1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
package ssh
import (
"bufio"
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/crc-org/crc/v2/pkg/crc/constants"
gossh "golang.org/x/crypto/ssh"
)
var (
ErrKeyGeneration = errors.New("Unable to generate key")
ErrPrivateKey = errors.New("Unable to marshal private key")
ErrPublicKey = errors.New("Unable to convert public key")
ErrUnableToWriteFile = errors.New("Unable to write file")
)
type KeyPair struct {
PrivateKey []byte
PublicKey []byte
}
// NewKeyPair generates a new SSH keypair
// This will return a private & public key encoded as DER.
func NewKeyPair() (keyPair *KeyPair, err error) {
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, ErrKeyGeneration
}
privDer, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, ErrPrivateKey
}
pubSSH, err := gossh.NewPublicKey(&priv.PublicKey)
if err != nil {
return nil, ErrPublicKey
}
return &KeyPair{
PrivateKey: privDer,
PublicKey: gossh.MarshalAuthorizedKey(pubSSH),
}, nil
}
// GenerateSSHKey generates SSH keypair based on path of the private key
// The public key would be generated to the same path with ".pub" added
func GenerateSSHKey(path string) error {
if _, err := os.Stat(path); err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("Desired directory for SSH keys does not exist: %s", err)
}
kp, err := NewKeyPair()
if err != nil {
return fmt.Errorf("Error generating key pair: %s", err)
}
if err := kp.WriteToFile(path, fmt.Sprintf("%s.pub", path)); err != nil {
return fmt.Errorf("Error writing keys to file(s): %s", err)
}
}
return nil
}
func RemoveCRCHostEntriesFromKnownHosts() error {
knownHostsPath := filepath.Join(constants.GetHomeDir(), ".ssh", "known_hosts")
if _, err := os.Stat(knownHostsPath); err != nil {
return nil
}
f, err := os.Open(knownHostsPath)
if err != nil {
return fmt.Errorf("Unable to open user's 'known_hosts' file: %w", err)
}
defer f.Close()
tempHostsFile, err := os.CreateTemp(filepath.Join(constants.GetHomeDir(), ".ssh"), "crc")
if err != nil {
return fmt.Errorf("Unable to create temp file: %w", err)
}
defer func() {
tempHostsFile.Close()
os.Remove(tempHostsFile.Name())
}()
if err := tempHostsFile.Chmod(0600); err != nil {
return fmt.Errorf("Error trying to change permissions for temp file: %w", err)
}
// return each line along with the newline '\n' marker
var splitFunc = func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
return i + 1, data[0 : i+1], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
var foundCRCEntries bool
scanner := bufio.NewScanner(f)
scanner.Split(splitFunc)
writer := bufio.NewWriter(tempHostsFile)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "[127.0.0.1]:2222") || strings.Contains(scanner.Text(), "192.168.130.11") {
foundCRCEntries = true
continue
}
if _, err := writer.WriteString(scanner.Text()); err != nil {
return fmt.Errorf("Error while writing hostsfile content to temp file: %w", err)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("Error while reading content from known_hosts file: %w", err)
}
if err := writer.Flush(); err != nil {
return fmt.Errorf("Error while flushing buffered content to temp file: %w", err)
}
if foundCRCEntries {
if err := f.Close(); err != nil {
return fmt.Errorf("Error closing known_hosts file: %w", err)
}
if err := tempHostsFile.Close(); err != nil {
return fmt.Errorf("Error closing temp file: %w", err)
}
return os.Rename(tempHostsFile.Name(), knownHostsPath)
}
return nil
}
|