Improve host key scanner, add Ed25519 generator

pull/32/head
Hidde Beydals 5 years ago
parent 3a8151bcc0
commit 6017946144

@ -2,10 +2,12 @@ package main
import (
"context"
"crypto/elliptic"
"fmt"
"io/ioutil"
"net/url"
"os"
"time"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"github.com/manifoldco/promptui"
@ -42,11 +44,19 @@ For private Git repositories, the basic authentication credentials are stored in
--url=https://github.com/stefanprodan/podinfo \
--tag-semver=">=3.2.0 <3.3.0"
# Create a source from a Git repository using SSH authentication
# Create a source from a Git repository using SSH authentication
create source git podinfo \
--url=ssh://git@github.com/stefanprodan/podinfo \
--branch=master
# Create a source from a Git repository using SSH authentication and an
# ECDSA P-521 curve public key
create source git podinfo \
--url=ssh://git@github.com/stefanprodan/podinfo \
--branch=master \
--ssh-key-algorithm=ecdsa \
--ssh-ecdsa-curve=p521
# Create a source from a Git repository using basic authentication
create source git podinfo \
--url=https://github.com/stefanprodan/podinfo \
@ -63,9 +73,9 @@ var (
sourceGitSemver string
sourceGitUsername string
sourceGitPassword string
sourceGitKeyAlgorithm PublicKeyAlgorithm
sourceGitRSABits RSAKeyBits
sourceGitECDSACurve ECDSACurve
sourceGitKeyAlgorithm PublicKeyAlgorithm = "rsa"
sourceGitRSABits RSAKeyBits = 2048
sourceGitECDSACurve = ECDSACurve{elliptic.P384()}
)
func init() {
@ -75,9 +85,9 @@ func init() {
createSourceGitCmd.Flags().StringVar(&sourceGitSemver, "tag-semver", "", "git tag semver range")
createSourceGitCmd.Flags().StringVarP(&sourceGitUsername, "username", "u", "", "basic authentication username")
createSourceGitCmd.Flags().StringVarP(&sourceGitPassword, "password", "p", "", "basic authentication password")
createSourceGitCmd.Flags().Var(&sourceGitKeyAlgorithm, "ssh-algorithm", "SSH public key algorithm")
createSourceGitCmd.Flags().Var(&sourceGitRSABits, "ssh-rsa-bits", "SSH RSA public key bit size")
createSourceGitCmd.Flags().Var(&sourceGitECDSACurve, "ssh-ecdsa-curve", "SSH ECDSA public key curve")
createSourceGitCmd.Flags().Var(&sourceGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description())
createSourceGitCmd.Flags().Var(&sourceGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description())
createSourceGitCmd.Flags().Var(&sourceGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description())
createSourceCmd.AddCommand(createSourceGitCmd)
}
@ -108,18 +118,11 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
withAuth := false
if u.Scheme == "ssh" {
var keyGen ssh.KeyPairGenerator
switch sourceGitKeyAlgorithm.String() {
case "rsa":
keyGen = ssh.NewRSAGenerator(int(sourceGitRSABits))
case "ecdsa":
keyGen = ssh.NewECDSAGenerator(sourceGitECDSACurve.Curve)
}
host := u.Host
if u.Port() == "" {
host = host + ":22"
}
if err := generateSSH(ctx, keyGen, name, host, tmpDir); err != nil {
if err := generateSSH(ctx, name, host); err != nil {
return err
}
withAuth = true
@ -212,13 +215,14 @@ func generateBasicAuth(ctx context.Context, name string) error {
return nil
}
func generateSSH(ctx context.Context, generator ssh.KeyPairGenerator, name, host, user string) error {
logGenerate("generating deploy key")
kp, err := generator.Generate()
func generateSSH(ctx context.Context, name, host string) error {
gen := getKeyPairGenerator()
logGenerate("generating deploy key pair")
pair, err := gen.Generate()
if err != nil {
return fmt.Errorf("SSH key pair generation failed: %w", err)
return fmt.Errorf("key pair generation failed: %w", err)
}
fmt.Printf("%s", kp.PublicKey)
fmt.Printf("%s", pair.PublicKey)
prompt := promptui.Prompt{
Label: "Have you added the deploy key to your repository",
@ -228,21 +232,21 @@ func generateSSH(ctx context.Context, generator ssh.KeyPairGenerator, name, host
return fmt.Errorf("aborting")
}
logAction("collecting SSH server public key for generated public key algorithm")
hostKey, err := ssh.ScanHostKey(host, user, kp)
logAction("collecting preferred public key from SSH server")
hostKey, err := ssh.ScanHostKey(host, 30*time.Second)
if err != nil {
return err
}
logSuccess("collected public key from SSH server")
logSuccess("collected public key from SSH server:")
fmt.Printf("%s", hostKey)
logAction("saving keys")
files := fmt.Sprintf("--from-literal=identity=\"%s\" --from-literal=identity.pub=\"%s\" --from-literal=known_hosts=\"%s\"",
kp.PublicKey, kp.PrivateKey, hostKey)
pair.PrivateKey, pair.PublicKey, hostKey)
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
namespace, name, files)
if _, err := utils.execCommand(ctx, ModeOS, secret); err != nil {
return fmt.Errorf("create secret failed")
return fmt.Errorf("failed to create secret")
}
return nil
}
@ -301,3 +305,16 @@ func isGitRepositoryReady(ctx context.Context, kubeClient client.Client, name, n
return false, nil
}
}
func getKeyPairGenerator() ssh.KeyPairGenerator {
var keyGen ssh.KeyPairGenerator
switch sourceGitKeyAlgorithm.String() {
case "rsa":
keyGen = ssh.NewRSAGenerator(int(sourceGitRSABits))
case "ecdsa":
keyGen = ssh.NewECDSAGenerator(sourceGitECDSACurve.Curve)
case "ed25519":
keyGen = ssh.NewEd25519Generator()
}
return keyGen
}

@ -7,7 +7,7 @@ import (
"strings"
)
var supportedPublicKeyAlgorithms = []string{"rsa", "ecdsa"}
var supportedPublicKeyAlgorithms = []string{"rsa", "ecdsa", "ed25519"}
type PublicKeyAlgorithm string
@ -17,8 +17,8 @@ func (a *PublicKeyAlgorithm) String() string {
func (a *PublicKeyAlgorithm) Set(str string) error {
if strings.TrimSpace(str) == "" {
*a = PublicKeyAlgorithm(supportedPublicKeyAlgorithms[0])
return nil
return fmt.Errorf("no public key algorithm given, must be one of: %s",
strings.Join(supportedPublicKeyAlgorithms, ", "))
}
for _, v := range supportedPublicKeyAlgorithms {
if str == v {
@ -26,17 +26,18 @@ func (a *PublicKeyAlgorithm) Set(str string) error {
return nil
}
}
return fmt.Errorf(
"unsupported public key algorithm '%s', must be one of: %s",
str,
strings.Join(supportedPublicKeyAlgorithms, ", "),
)
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
@ -65,37 +66,47 @@ 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{
"P-256": elliptic.P256(),
"P-384": elliptic.P384(),
"P-521": elliptic.P521(),
"p256": elliptic.P256(),
"p384": elliptic.P384(),
"p521": elliptic.P521(),
}
func (c *ECDSACurve) String() string {
if c == nil || c.Curve == nil {
if c.Curve == nil {
return ""
}
return c.Curve.Params().Name
return strings.ToLower(strings.Replace(c.Curve.Params().Name, "-", "", 1))
}
func (c *ECDSACurve) Set(str string) error {
if strings.TrimSpace(str) == "" {
*c = ECDSACurve{supportedECDSACurves["P-384"]}
if v, ok := supportedECDSACurves[str]; ok {
*c = ECDSACurve{v}
return nil
}
for k, v := range supportedECDSACurves {
if k == str {
*c = ECDSACurve{v}
return nil
}
}
return fmt.Errorf("unsupported curve '%s', should be one of: P-256, P-384, P-521", str)
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
}

@ -4,27 +4,20 @@ 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
// algorithm of the given key pair. Any errors (e.g. authentication
// failures) are ignored, except if no key could be collected from the
// host.
func ScanHostKey(host string, user string, pair *KeyPair) ([]byte, error) {
signer, err := ssh.ParsePrivateKey(pair.PrivateKey)
if err != nil {
return nil, err
}
// 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 := &collector{}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: col.StoreKey(),
HostKeyCallback: col.StoreKey(),
Timeout: timeout,
}
client, err := ssh.Dial("tcp", host, config)
if err == nil {
@ -40,6 +33,10 @@ type collector 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 *collector) StoreKey() ssh.HostKeyCallback {
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
c.knownKeys = append(

@ -2,6 +2,7 @@ package ssh
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
@ -79,6 +80,31 @@ func (g *ECDSAGenerator) Generate() (*KeyPair, error) {
}, 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 {

Loading…
Cancel
Save