diff --git a/Dockerfile b/Dockerfile index 707b79f..ccde4ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ run apt-get update \ run yes |apt-get install -y grub2 grub-pc-bin grub-efi-amd64-bin \ && apt-get clean -run apt-get install -y ca-certificates curl \ +run apt-get install -y ca-certificates curl openssh-client \ && apt-get clean run curl -L https://github.com/vmware/govmomi/releases/download/v0.21.0/govc_linux_amd64.gz | gunzip > /bin/govc && chmod +x /bin/govc diff --git a/cmd/dkl-dir2config/render-context.go b/cmd/dkl-dir2config/render-context.go index 508efbd..3f5f786 100644 --- a/cmd/dkl-dir2config/render-context.go +++ b/cmd/dkl-dir2config/render-context.go @@ -257,6 +257,11 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]interface{}) map[strin return getKeyCert(name, "tls_dir") }, + "ssh_host_keys": func(dir string) (s string) { + return fmt.Sprintf("{{ ssh_host_keys %q %q %q}}", + dir, cluster, ctx.Host.Name) + }, + "hosts_of_group": func() (hosts []interface{}) { hosts = make([]interface{}, 0) diff --git a/cmd/dkl-local-server/render-context.go b/cmd/dkl-local-server/render-context.go index 097bfea..5b17522 100644 --- a/cmd/dkl-local-server/render-context.go +++ b/cmd/dkl-local-server/render-context.go @@ -239,6 +239,33 @@ func (ctx *renderContext) templateFuncs() map[string]interface{} { }, }) }, + + "ssh_host_keys": func(dir, cluster, host string) (s string, err error) { + pairs, err := secretData.SSHKeyPairs(cluster, host) + if err != nil { + return + } + + files := make([]config.FileDef, 0, len(pairs)*2) + + for _, pair := range pairs { + basePath := path.Join(dir, "ssh_host_"+pair.Type+"_key") + files = append(files, []config.FileDef{ + { + Path: basePath, + Mode: 0600, + Content: pair.Private, + }, + { + Path: basePath + ".pub", + Mode: 0644, + Content: pair.Public, + }, + }...) + } + + return asYaml(files) + }, } } diff --git a/cmd/dkl-local-server/secrets.go b/cmd/dkl-local-server/secrets.go index 167b809..acb45c1 100644 --- a/cmd/dkl-local-server/secrets.go +++ b/cmd/dkl-local-server/secrets.go @@ -26,6 +26,7 @@ import ( var ( secretData *SecretData + DontSave = false ) type SecretData struct { @@ -37,9 +38,10 @@ type SecretData struct { } type ClusterSecrets struct { - CAs map[string]*CA - Tokens map[string]string - Passwords map[string]string + CAs map[string]*CA + Tokens map[string]string + Passwords map[string]string + SSHKeyPairs map[string][]SSHKeyPair } type CA struct { @@ -92,6 +94,10 @@ func (sd *SecretData) Changed() bool { } func (sd *SecretData) Save() error { + if DontSave { + return nil + } + sd.l.Lock() defer sd.l.Unlock() diff --git a/cmd/dkl-local-server/ssh-secrets.go b/cmd/dkl-local-server/ssh-secrets.go new file mode 100644 index 0000000..5267472 --- /dev/null +++ b/cmd/dkl-local-server/ssh-secrets.go @@ -0,0 +1,167 @@ +package main + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "fmt" + "io/ioutil" + "os" + "os/exec" +) + +type SSHKeyPair struct { + Type string + Public string + Private string +} + +func (sd *SecretData) SSHKeyPairs(cluster, host string) (pairs []SSHKeyPair, err error) { + cs := sd.cluster(cluster) + + if cs.SSHKeyPairs == nil { + cs.SSHKeyPairs = map[string][]SSHKeyPair{} + } + + outFile, err := ioutil.TempFile("/tmp", "dls-key.") + if err != nil { + return + } + + outPath := outFile.Name() + + removeTemp := func() { + os.Remove(outPath) + os.Remove(outPath + ".pub") + } + + defer removeTemp() + + pairs = cs.SSHKeyPairs[host] + + didGenerate := false + +genLoop: + for _, keyType := range []string{ + "rsa", + "dsa", + "ecdsa", + "ed25519", + } { + for _, pair := range pairs { + if pair.Type == keyType { + continue genLoop + } + } + + didGenerate = true + + removeTemp() + + var out, privKey, pubKey []byte + + out, err = exec.Command("ssh-keygen", + "-N", "", + "-C", "root@"+host, + "-f", outPath, + "-t", keyType).CombinedOutput() + if err != nil { + err = fmt.Errorf("ssh-keygen failed: %v: %s", err, string(out)) + return + } + + privKey, err = ioutil.ReadFile(outPath) + if err != nil { + return + } + + os.Remove(outPath) + + pubKey, err = ioutil.ReadFile(outPath + ".pub") + if err != nil { + return + } + + os.Remove(outPath + ".pub") + + pairs = append(pairs, SSHKeyPair{ + Type: keyType, + Public: string(pubKey), + Private: string(privKey), + }) + } + + if didGenerate { + cs.SSHKeyPairs[host] = pairs + err = sd.Save() + } + + return +} + +func sshKeyGenDSA() (data []byte, pubKey interface{}, err error) { + privKey := &dsa.PrivateKey{} + + err = dsa.GenerateParameters(&privKey.Parameters, rand.Reader, dsa.L1024N160) + if err != nil { + return + } + + err = dsa.GenerateKey(privKey, rand.Reader) + if err != nil { + return + } + + data, err = asn1.Marshal(*privKey) + //data, err = x509.MarshalPKCS8PrivateKey(privKey) + if err != nil { + return + } + + pubKey = privKey.PublicKey + return +} + +func sshKeyGenRSA() (data []byte, pubKey interface{}, err error) { + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return + } + + data = x509.MarshalPKCS1PrivateKey(privKey) + pubKey = privKey.Public() + + return +} + +func sshKeyGenECDSA() (data []byte, pubKey interface{}, err error) { + privKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return + } + + data, err = x509.MarshalPKCS8PrivateKey(privKey) + if err != nil { + return + } + + pubKey = privKey.Public() + + return +} + +func sshKeyGenED25519() (data []byte, pubKey interface{}, err error) { + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + + data, err = x509.MarshalPKCS8PrivateKey(privKey) + if err != nil { + return + } + + return +} diff --git a/cmd/dkl-local-server/ssh-secrets_test.go b/cmd/dkl-local-server/ssh-secrets_test.go new file mode 100644 index 0000000..9b14186 --- /dev/null +++ b/cmd/dkl-local-server/ssh-secrets_test.go @@ -0,0 +1,17 @@ +package main + +import "testing" + +func init() { + DontSave = true +} + +func TestSSHKeyGet(t *testing.T) { + sd := &SecretData{ + clusters: make(map[string]*ClusterSecrets), + } + + if _, err := sd.SSHKeyPairs("test", "host"); err != nil { + t.Error(err) + } +} diff --git a/go.mod b/go.mod index e686507..f0d595e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module novit.nc/direktil/local-server require ( + github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e github.com/emicklei/go-restful v2.10.0+incompatible @@ -14,7 +15,7 @@ require ( github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34 github.com/oklog/ulid v1.3.1 github.com/pierrec/lz4 v2.3.0+incompatible - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect golang.org/x/sys v0.0.0-20191018095205-727590c5006e // indirect gopkg.in/src-d/go-billy.v4 v4.3.2 diff --git a/go.sum b/go.sum index 46a9c8f..2a397f4 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=