Files
local-server/cmd/dkl-local-server/ssh-secrets.go

212 lines
3.9 KiB
Go
Raw Normal View History

2019-12-03 11:03:20 +01:00
package main
import (
2025-06-28 11:04:44 +02:00
"bytes"
"crypto"
2019-12-03 11:03:20 +01:00
"crypto/ed25519"
2025-06-28 11:04:44 +02:00
"encoding/pem"
2019-12-03 11:03:20 +01:00
"fmt"
2025-06-28 11:04:44 +02:00
"io"
2019-12-03 11:03:20 +01:00
"os"
"os/exec"
2025-06-28 11:04:44 +02:00
"strconv"
"strings"
"time"
"golang.org/x/crypto/ssh"
2019-12-03 11:03:20 +01:00
)
var sshHostKeys = KVSecrets[[]SSHKeyPair]{"hosts/ssh-host-keys"}
2019-12-03 11:03:20 +01:00
type SSHKeyPair struct {
Type string
Public string
Private string
}
func getSSHKeyPairs(host string) (pairs []SSHKeyPair, err error) {
pairs, _, err = sshHostKeys.Get(host)
2019-12-03 11:03:20 +01:00
didGenerate := false
genLoop:
for _, keyType := range []string{
"rsa",
"dsa",
"ecdsa",
"ed25519",
} {
for _, pair := range pairs {
if pair.Type == keyType {
continue genLoop
}
}
err = func() (err error) {
2025-01-26 11:31:04 +01:00
outFile, err := os.CreateTemp("/tmp", "dls-key.")
if err != nil {
return
}
2019-12-03 11:03:20 +01:00
outPath := outFile.Name()
2019-12-03 11:03:20 +01:00
removeTemp := func() {
os.Remove(outPath)
os.Remove(outPath + ".pub")
}
2019-12-03 11:03:20 +01:00
2023-02-12 18:59:14 +01:00
removeTemp()
defer removeTemp()
2019-12-03 11:03:20 +01:00
var out, privKey, pubKey []byte
2023-02-12 18:59:14 +01:00
cmd := exec.Command("ssh-keygen",
"-N", "",
"-C", "root@"+host,
"-f", outPath,
2023-02-12 18:59:14 +01:00
"-t", keyType)
out, err = cmd.CombinedOutput()
if err != nil {
err = fmt.Errorf("ssh-keygen failed: %v: %s", err, string(out))
return
}
2025-01-26 11:31:04 +01:00
privKey, err = os.ReadFile(outPath)
if err != nil {
return
}
2025-01-26 11:31:04 +01:00
pubKey, err = os.ReadFile(outPath + ".pub")
if err != nil {
return
}
2019-12-03 11:03:20 +01:00
pairs = append(pairs, SSHKeyPair{
Type: keyType,
Public: string(pubKey),
Private: string(privKey),
})
didGenerate = true
return
}()
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
}
if didGenerate {
err = sshHostKeys.Put(host, pairs)
if err != nil {
return
}
2019-12-03 11:03:20 +01:00
}
return
}
2025-06-28 11:04:44 +02:00
var sshCAKeys = KVSecrets[string]{"ssh-ca-keys"}
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
func sshCAKey(cluster string) (caKeyPem string, err error) {
storeKey := "clusters/" + cluster
caKeyPem, _, err = sshCAKeys.Get(storeKey)
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
if caKeyPem == "" {
_, pk, err := ed25519.GenerateKey(nil)
if err != nil {
return "", err
}
pemBlock, err := ssh.MarshalPrivateKey(crypto.PrivateKey(pk), "")
if err != nil {
return "", err
}
caKeyPem = string(pem.EncodeToMemory(pemBlock))
sshCAKeys.Put(storeKey, caKeyPem)
}
return
}
func sshCAPubKey(cluster string) (pubKey []byte, err error) {
keyPem, err := sshCAKey(cluster)
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
k, err := ssh.ParsePrivateKey([]byte(keyPem))
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
pubKey = ssh.MarshalAuthorizedKey(k.PublicKey())
2019-12-03 11:03:20 +01:00
return
}
2025-06-28 11:04:44 +02:00
// principal: user (login) to allow (ie: "root")
// validity: ssh-keygen validity string (ie: "+1h", "202506280811:202506281011", ""=forever)
// options: ssh-keygen options (ie: "force-command=/bin/date +\"%F %T\"", "source-address=192.168.1.0/24,192.168.42.0/24"
func sshCASign(cluster string, userPubKey []byte, principal, validity string, options ...string) (cert []byte, err error) {
caKey, err := sshCAKey(cluster)
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
_, identity, _, _, err := ssh.ParseAuthorizedKey(userPubKey)
if err != nil {
return
}
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
userPubKeyFile, err := os.CreateTemp("/tmp", "user.pub")
if err != nil {
return
}
defer os.Remove(userPubKeyFile.Name())
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
_, err = io.Copy(userPubKeyFile, bytes.NewBuffer(userPubKey))
userPubKeyFile.Close()
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
err = os.WriteFile(userPubKeyFile.Name(), userPubKey, 0600)
2019-12-03 11:03:20 +01:00
if err != nil {
return
}
2025-06-28 11:04:44 +02:00
serial := strconv.FormatInt(time.Now().Unix(), 10)
cmd := exec.Command("ssh-keygen", "-q", "-s", "/dev/stdin", "-I", identity, "-z", serial, "-n", principal)
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
if validity != "" {
cmd.Args = append(cmd.Args, "-V", validity)
}
for _, opt := range options {
cmd.Args = append(cmd.Args, "-O", opt)
}
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
cmd.Args = append(cmd.Args, userPubKeyFile.Name())
2019-12-03 11:03:20 +01:00
2025-06-28 11:04:44 +02:00
stderr := new(bytes.Buffer)
cmd.Stdin = bytes.NewBuffer([]byte(caKey))
cmd.Stderr = stderr
err = cmd.Run()
2019-12-03 11:03:20 +01:00
if err != nil {
2025-06-28 11:04:44 +02:00
err = fmt.Errorf("ssh-keygen sign failed: %s", strings.TrimSpace(stderr.String()))
2019-12-03 11:03:20 +01:00
return
}
2025-06-28 11:04:44 +02:00
certFile := userPubKeyFile.Name() + "-cert.pub"
cert, err = os.ReadFile(certFile)
os.Remove(certFile)
2019-12-03 11:03:20 +01:00
return
}