goldwarden-vaultwarden-bitw.../agent/ssh/ssh.go

179 lines
4.3 KiB
Go

package ssh
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"net"
"os"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
var log = llamalog.NewLogger("Goldwarden", "SSH")
type vaultAgent struct {
vault *vault.Vault
unlockRequestAction func() bool
context sockets.CallingContext
}
func (vaultAgent) Add(key agent.AddedKey) error {
return nil
}
func (vaultAgent vaultAgent) List() ([]*agent.Key, error) {
if vaultAgent.vault.Keyring.IsLocked() {
if !vaultAgent.unlockRequestAction() {
return nil, errors.New("vault is locked")
}
}
vaultSSHKeys := (*vaultAgent.vault).GetSSHKeys()
var sshKeys []*agent.Key
for _, vaultSSHKey := range vaultSSHKeys {
signer, err := ssh.ParsePrivateKey([]byte(vaultSSHKey.Key))
if err != nil {
continue
}
pub := signer.PublicKey()
sshKeys = append(sshKeys, &agent.Key{
Format: pub.Type(),
Blob: pub.Marshal(),
Comment: vaultSSHKey.Name})
}
return sshKeys, nil
}
func (vaultAgent) Lock(passphrase []byte) error {
return nil
}
func (vaultAgent) Remove(key ssh.PublicKey) error {
return nil
}
func (vaultAgent) RemoveAll() error {
return nil
}
func Eq(a, b ssh.PublicKey) bool {
return 0 == bytes.Compare(a.Marshal(), b.Marshal())
}
func (vaultAgent vaultAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
log.Info("Sign Request for key: %s", ssh.FingerprintSHA256(key))
if vaultAgent.vault.Keyring.IsLocked() {
if !vaultAgent.unlockRequestAction() {
return nil, errors.New("vault is locked")
}
}
var signer ssh.Signer
var sshKey *vault.SSHKey
vaultSSHKeys := (*vaultAgent.vault).GetSSHKeys()
for _, vaultSSHKey := range vaultSSHKeys {
sg, err := ssh.ParsePrivateKey([]byte(vaultSSHKey.Key))
if err != nil {
return nil, err
}
if Eq(sg.PublicKey(), key) {
signer = sg
sshKey = &vaultSSHKey
break
}
}
message := fmt.Sprintf("%s on %s>%s>%s is requesting signage with key %s", vaultAgent.context.UserName, vaultAgent.context.GrandParentProcessName, vaultAgent.context.ParentProcessName, vaultAgent.context.ProcessName, sshKey.Name)
if approved, err := systemauth.GetApproval("SSH Key Signing Request", message); err != nil || !approved {
log.Info("Sign Request for key: %s denied", sshKey.Name)
return nil, errors.New("Approval not given")
}
if !systemauth.CheckBiometrics(systemauth.SSHKey) {
log.Info("Sign Request for key: %s denied", key.Marshal())
return nil, errors.New("Biometrics not checked")
}
var rand = rand.Reader
log.Info("Sign Request for key: %s %s accepted", ssh.FingerprintSHA256(key), sshKey.Name)
return signer.Sign(rand, data)
}
func (vaultAgent) Signers() ([]ssh.Signer, error) {
return []ssh.Signer{}, nil
}
func (vaultAgent) Unlock(passphrase []byte) error {
return nil
}
type SSHAgentServer struct {
vault *vault.Vault
unlockRequestAction func() bool
}
func (v *SSHAgentServer) SetUnlockRequestAction(action func() bool) {
v.unlockRequestAction = action
}
func NewVaultAgent(vault *vault.Vault) SSHAgentServer {
return SSHAgentServer{
vault: vault,
unlockRequestAction: func() bool {
log.Info("Unlock Request, but no action defined")
return false
},
}
}
func (v SSHAgentServer) Serve() {
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
path := home + "/.goldwarden-ssh-agent.sock"
if _, err := os.Stat(path); err == nil {
if err := os.Remove(path); err != nil {
log.Error("Could not remove old socket file: %s", err)
return
}
}
listener, err := net.Listen("unix", path)
if err != nil {
panic(err)
}
log.Info("SSH Agent listening on %s", path)
for {
var conn, err = listener.Accept()
if err != nil {
panic(err)
}
callingContext := sockets.GetCallingContext(conn)
log.Info("SSH Agent connection from %s>%s>%s \nby user %s", callingContext.GrandParentProcessName, callingContext.ParentProcessName, callingContext.ProcessName, callingContext.UserName)
log.Info("SSH Agent connection accepted")
go agent.ServeAgent(vaultAgent{
vault: v.vault,
unlockRequestAction: v.unlockRequestAction,
context: callingContext,
}, conn)
}
}