add ssh user CA support
This commit is contained in:
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user