mirror of https://github.com/fluxcd/flux2.git
Merge pull request #32 from fluxcd/go-native-ssh
commit
9e31bbe716
@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var supportedPublicKeyAlgorithms = []string{"rsa", "ecdsa", "ed25519"}
|
||||
|
||||
type PublicKeyAlgorithm string
|
||||
|
||||
func (a *PublicKeyAlgorithm) String() string {
|
||||
return string(*a)
|
||||
}
|
||||
|
||||
func (a *PublicKeyAlgorithm) Set(str string) error {
|
||||
if strings.TrimSpace(str) == "" {
|
||||
return fmt.Errorf("no public key algorithm given, must be one of: %s",
|
||||
strings.Join(supportedPublicKeyAlgorithms, ", "))
|
||||
}
|
||||
for _, v := range supportedPublicKeyAlgorithms {
|
||||
if str == v {
|
||||
*a = PublicKeyAlgorithm(str)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unsupported public key algorithm '%s', must be one of: %s",
|
||||
str, strings.Join(supportedPublicKeyAlgorithms, ", "))
|
||||
}
|
||||
|
||||
func (a *PublicKeyAlgorithm) Type() string {
|
||||
return "publicKeyAlgorithm"
|
||||
}
|
||||
|
||||
func (a *PublicKeyAlgorithm) Description() string {
|
||||
return fmt.Sprintf("SSH public key algorithm (%s)", strings.Join(supportedPublicKeyAlgorithms, ", "))
|
||||
}
|
||||
|
||||
var defaultRSAKeyBits = 2048
|
||||
|
||||
type RSAKeyBits int
|
||||
|
||||
func (b *RSAKeyBits) String() string {
|
||||
return strconv.Itoa(int(*b))
|
||||
}
|
||||
|
||||
func (b *RSAKeyBits) Set(str string) error {
|
||||
if strings.TrimSpace(str) == "" {
|
||||
*b = RSAKeyBits(defaultRSAKeyBits)
|
||||
return nil
|
||||
}
|
||||
bits, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bits%8 != 0 {
|
||||
return fmt.Errorf("RSA key bit size should be a multiples of 8")
|
||||
}
|
||||
*b = RSAKeyBits(bits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *RSAKeyBits) Type() string {
|
||||
return "rsaKeyBits"
|
||||
}
|
||||
|
||||
func (b *RSAKeyBits) Description() string {
|
||||
return "SSH RSA public key bit size (multiplies of 8)"
|
||||
}
|
||||
|
||||
type ECDSACurve struct {
|
||||
elliptic.Curve
|
||||
}
|
||||
|
||||
var supportedECDSACurves = map[string]elliptic.Curve{
|
||||
"p256": elliptic.P256(),
|
||||
"p384": elliptic.P384(),
|
||||
"p521": elliptic.P521(),
|
||||
}
|
||||
|
||||
func (c *ECDSACurve) String() string {
|
||||
if c.Curve == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(strings.Replace(c.Curve.Params().Name, "-", "", 1))
|
||||
}
|
||||
|
||||
func (c *ECDSACurve) Set(str string) error {
|
||||
if v, ok := supportedECDSACurves[str]; ok {
|
||||
*c = ECDSACurve{v}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported curve '%s', should be one of: %s", str, strings.Join(ecdsaCurves(), ", "))
|
||||
}
|
||||
|
||||
func (c *ECDSACurve) Type() string {
|
||||
return "ecdsaCurve"
|
||||
}
|
||||
|
||||
func (c *ECDSACurve) Description() string {
|
||||
return fmt.Sprintf("SSH ECDSA public key curve (%s)", strings.Join(ecdsaCurves(), ", "))
|
||||
}
|
||||
|
||||
func ecdsaCurves() []string {
|
||||
keys := make([]string, 0, len(supportedECDSACurves))
|
||||
for k := range supportedECDSACurves {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/knownhosts"
|
||||
)
|
||||
|
||||
// ScanHostKey collects the given host's preferred public key for the
|
||||
// Any errors (e.g. authentication failures) are ignored, except if
|
||||
// no key could be collected from the host.
|
||||
func ScanHostKey(host string, timeout time.Duration) ([]byte, error) {
|
||||
col := &HostKeyCollector{}
|
||||
config := &ssh.ClientConfig{
|
||||
HostKeyCallback: col.StoreKey(),
|
||||
Timeout: timeout,
|
||||
}
|
||||
client, err := ssh.Dial("tcp", host, config)
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
}
|
||||
if len(col.knownKeys) > 0 {
|
||||
return col.knownKeys, nil
|
||||
}
|
||||
return col.knownKeys, err
|
||||
}
|
||||
|
||||
// HostKeyCollector offers a StoreKey method which provides an
|
||||
// HostKeyCallBack to collect public keys from an SSH server.
|
||||
type HostKeyCollector struct {
|
||||
knownKeys []byte
|
||||
}
|
||||
|
||||
// StoreKey stores the public key in bytes as returned by the host.
|
||||
// To collect multiple public key types from the host, multiple
|
||||
// SSH dials need with the ClientConfig HostKeyAlgorithms set to
|
||||
// the algorithm you want to collect.
|
||||
func (c *HostKeyCollector) StoreKey() ssh.HostKeyCallback {
|
||||
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
c.knownKeys = append(
|
||||
c.knownKeys,
|
||||
fmt.Sprintf("%s %s %s\n", knownhosts.Normalize(hostname), key.Type(), base64.StdEncoding.EncodeToString(key.Marshal()))...,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetKnownKeys returns the collected public keys in bytes.
|
||||
func (c *HostKeyCollector) GetKnownKeys() []byte {
|
||||
return c.knownKeys
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// KeyPair holds the public and private key PEM block bytes.
|
||||
type KeyPair struct {
|
||||
PublicKey []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type KeyPairGenerator interface {
|
||||
Generate() (*KeyPair, error)
|
||||
}
|
||||
|
||||
type RSAGenerator struct {
|
||||
bits int
|
||||
}
|
||||
|
||||
func NewRSAGenerator(bits int) KeyPairGenerator {
|
||||
return &RSAGenerator{bits}
|
||||
}
|
||||
|
||||
func (g *RSAGenerator) Generate() (*KeyPair, error) {
|
||||
pk, err := rsa.GenerateKey(rand.Reader, g.bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pk.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := generatePublicKey(&pk.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := encodePrivateKeyToPEM(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KeyPair{
|
||||
PublicKey: pub,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ECDSAGenerator struct {
|
||||
c elliptic.Curve
|
||||
}
|
||||
|
||||
func NewECDSAGenerator(c elliptic.Curve) KeyPairGenerator {
|
||||
return &ECDSAGenerator{c}
|
||||
}
|
||||
|
||||
func (g *ECDSAGenerator) Generate() (*KeyPair, error) {
|
||||
pk, err := ecdsa.GenerateKey(g.c, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := generatePublicKey(&pk.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := encodePrivateKeyToPEM(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KeyPair{
|
||||
PublicKey: pub,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Ed25519Generator struct{}
|
||||
|
||||
func NewEd25519Generator() KeyPairGenerator {
|
||||
return &Ed25519Generator{}
|
||||
}
|
||||
|
||||
func (g *Ed25519Generator) Generate() (*KeyPair, error) {
|
||||
pk, pv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := generatePublicKey(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := encodePrivateKeyToPEM(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KeyPair{
|
||||
PublicKey: pub,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generatePublicKey(pk interface{}) ([]byte, error) {
|
||||
b, err := ssh.NewPublicKey(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k := ssh.MarshalAuthorizedKey(b)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// encodePrivateKeyToPEM encodes the given private key to a PEM block.
|
||||
// The encoded format is PKCS#8 for universal support of the most
|
||||
// common key types (rsa, ecdsa, ed25519).
|
||||
func encodePrivateKeyToPEM(pk interface{}) ([]byte, error) {
|
||||
b, err := x509.MarshalPKCS8PrivateKey(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block := pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: b,
|
||||
}
|
||||
return pem.EncodeToMemory(&block), nil
|
||||
}
|
Loading…
Reference in New Issue