add ssh user CA support

This commit is contained in:
Mikaël Cluseau
2025-06-28 11:04:44 +02:00
parent 4b05458cec
commit af41df6ab4
7 changed files with 172 additions and 77 deletions

View File

@ -1,17 +1,19 @@
package main
import (
"crypto/dsa"
"crypto/ecdsa"
"bytes"
"crypto"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
var sshHostKeys = KVSecrets[[]SSHKeyPair]{"hosts/ssh-host-keys"}
@ -104,64 +106,106 @@ genLoop:
return
}
func sshKeyGenDSA() (data []byte, pubKey interface{}, err error) {
privKey := &dsa.PrivateKey{}
var sshCAKeys = KVSecrets[string]{"ssh-ca-keys"}
err = dsa.GenerateParameters(&privKey.Parameters, rand.Reader, dsa.L1024N160)
func sshCAKey(cluster string) (caKeyPem string, err error) {
storeKey := "clusters/" + cluster
caKeyPem, _, err = sshCAKeys.Get(storeKey)
if err != nil {
return
}
err = dsa.GenerateKey(privKey, rand.Reader)
if err != nil {
return
}
if caKeyPem == "" {
_, pk, err := ed25519.GenerateKey(nil)
if err != nil {
return "", err
}
data, err = asn1.Marshal(*privKey)
//data, err = x509.MarshalPKCS8PrivateKey(privKey)
if err != nil {
return
}
pemBlock, err := ssh.MarshalPrivateKey(crypto.PrivateKey(pk), "")
if err != nil {
return "", err
}
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
caKeyPem = string(pem.EncodeToMemory(pemBlock))
sshCAKeys.Put(storeKey, caKeyPem)
}
return
}
func sshCAPubKey(cluster string) (pubKey []byte, err error) {
keyPem, err := sshCAKey(cluster)
if err != nil {
return
}
k, err := ssh.ParsePrivateKey([]byte(keyPem))
if err != nil {
return
}
pubKey = ssh.MarshalAuthorizedKey(k.PublicKey())
return
}
// 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)
if err != nil {
return
}
_, identity, _, _, err := ssh.ParseAuthorizedKey(userPubKey)
if err != nil {
return
}
userPubKeyFile, err := os.CreateTemp("/tmp", "user.pub")
if err != nil {
return
}
defer os.Remove(userPubKeyFile.Name())
_, err = io.Copy(userPubKeyFile, bytes.NewBuffer(userPubKey))
userPubKeyFile.Close()
if err != nil {
return
}
err = os.WriteFile(userPubKeyFile.Name(), userPubKey, 0600)
if err != nil {
return
}
serial := strconv.FormatInt(time.Now().Unix(), 10)
cmd := exec.Command("ssh-keygen", "-q", "-s", "/dev/stdin", "-I", identity, "-z", serial, "-n", principal)
if validity != "" {
cmd.Args = append(cmd.Args, "-V", validity)
}
for _, opt := range options {
cmd.Args = append(cmd.Args, "-O", opt)
}
cmd.Args = append(cmd.Args, userPubKeyFile.Name())
stderr := new(bytes.Buffer)
cmd.Stdin = bytes.NewBuffer([]byte(caKey))
cmd.Stderr = stderr
err = cmd.Run()
if err != nil {
err = fmt.Errorf("ssh-keygen sign failed: %s", strings.TrimSpace(stderr.String()))
return
}
certFile := userPubKeyFile.Name() + "-cert.pub"
cert, err = os.ReadFile(certFile)
os.Remove(certFile)
return
}