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

518 lines
13 KiB
Go
Raw Normal View History

2023-07-17 03:23:26 +02:00
package agent
import (
"context"
2024-02-09 19:32:01 +01:00
"crypto/subtle"
2023-07-17 03:23:26 +02:00
"encoding/json"
"fmt"
"net"
"os"
"time"
"github.com/quexten/goldwarden/agent/actions"
"github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/notify"
2023-08-24 03:22:03 +02:00
"github.com/quexten/goldwarden/agent/processsecurity"
2023-07-17 03:23:26 +02:00
"github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/ssh"
"github.com/quexten/goldwarden/agent/systemauth"
2024-02-09 00:24:28 +01:00
"github.com/quexten/goldwarden/agent/systemauth/pinentry"
2023-07-17 03:23:26 +02:00
"github.com/quexten/goldwarden/agent/vault"
2023-09-20 03:05:44 +02:00
"github.com/quexten/goldwarden/ipc/messages"
2023-08-21 18:37:34 +02:00
"github.com/quexten/goldwarden/logging"
2023-07-17 03:23:26 +02:00
)
const (
FullSyncInterval = 60 * time.Minute
2024-01-04 21:53:38 +01:00
TokenRefreshInterval = 10 * time.Minute
2023-07-17 03:23:26 +02:00
)
2023-08-21 18:37:34 +02:00
var log = logging.GetLogger("Goldwarden", "Agent")
2023-07-17 03:23:26 +02:00
func writeError(c net.Conn, errMsg error) error {
2023-09-20 03:05:44 +02:00
payload := messages.ActionResponse{
2023-07-17 03:23:26 +02:00
Success: false,
Message: errMsg.Error(),
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return err
}
_, err = c.Write(payloadBytes)
if err != nil {
return err
}
return nil
}
func serveAgentSession(c net.Conn, vault *vault.Vault, cfg *config.Config) {
2023-07-17 03:23:26 +02:00
for {
buf := make([]byte, 1024*1024)
nr, err := c.Read(buf)
if err != nil {
return
}
data := buf[0:nr]
2023-09-20 03:05:44 +02:00
var msg messages.IPCMessage
2023-07-17 03:23:26 +02:00
err = json.Unmarshal(data, &msg)
if err != nil {
writeError(c, err)
continue
}
2024-02-09 00:24:28 +01:00
// todo refactor to other file
if msg.Type == messages.MessageTypeForEmptyPayload(messages.SessionAuthRequest{}) {
2024-02-09 19:46:29 +01:00
if cfg.ConfigFile.RuntimeConfig.DaemonAuthToken == "" {
return
}
req := messages.ParsePayload(msg).(messages.SessionAuthRequest)
2024-02-09 19:32:01 +01:00
verified := subtle.ConstantTimeCompare([]byte(cfg.ConfigFile.RuntimeConfig.DaemonAuthToken), []byte(req.Token)) == 1
payload := messages.SessionAuthResponse{
2024-02-09 19:32:01 +01:00
Verified: verified,
}
2024-02-09 19:32:01 +01:00
log.Info("Verified: %t", verified)
callingContext := sockets.GetCallingContext(c)
2024-02-09 19:32:01 +01:00
if verified {
systemauth.CreatePinSession(callingContext, 365*24*time.Hour) // permanent session
}
responsePayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(responsePayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
continue
}
2024-02-09 00:24:28 +01:00
// todo refactor to other file
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryRegistrationRequest{}) {
2024-02-09 19:46:29 +01:00
// todo lockdown this method better
if cfg.ConfigFile.RuntimeConfig.DaemonAuthToken == "" {
return
}
2024-02-09 00:24:28 +01:00
log.Info("Received pinentry registration request")
getPasswordChan := make(chan struct {
title string
description string
})
getPasswordReturnChan := make(chan struct {
password string
err error
})
getApprovalChan := make(chan struct {
title string
description string
})
getApprovalReturnChan := make(chan struct {
approved bool
err error
})
pe := pinentry.Pinentry{
GetPassword: func(title string, description string) (string, error) {
getPasswordChan <- struct {
title string
description string
}{title, description}
returnValue := <-getPasswordReturnChan
return returnValue.password, returnValue.err
},
GetApproval: func(title string, description string) (bool, error) {
getApprovalChan <- struct {
title string
description string
}{title, description}
returnValue := <-getApprovalReturnChan
return returnValue.approved, returnValue.err
},
}
pinnentrySetError := pinentry.SetExternalPinentry(pe)
payload := messages.PinentryRegistrationResponse{
Success: pinnentrySetError == nil,
}
2024-02-09 17:26:41 +01:00
log.Info("Pinentry registration success: %t", payload.Success)
2024-02-09 00:24:28 +01:00
responsePayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(responsePayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
_, err = c.Write([]byte("\n"))
time.Sleep(50 * time.Millisecond) //todo fix properly
if pinnentrySetError != nil {
return
}
for {
fmt.Println("Waiting for pinentry request")
select {
case getPasswordRequest := <-getPasswordChan:
log.Info("Received getPassword request")
payload := messages.PinentryPinRequest{
Message: getPasswordRequest.description,
}
payloadPayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(payloadPayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
buf := make([]byte, 1024*1024)
nr, err := c.Read(buf)
if err != nil {
return
}
data := buf[0:nr]
var msg messages.IPCMessage
err = json.Unmarshal(data, &msg)
if err != nil {
writeError(c, err)
continue
}
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryPinResponse{}) {
getPasswordResponse := messages.ParsePayload(msg).(messages.PinentryPinResponse)
getPasswordReturnChan <- struct {
password string
err error
}{getPasswordResponse.Pin, nil}
}
2024-02-09 14:03:27 +01:00
case getApprovalRequest := <-getApprovalChan:
log.Info("Received getApproval request")
payload := messages.PinentryApprovalRequest{
Message: getApprovalRequest.description,
}
payloadPayload, err := messages.IPCMessageFromPayload(payload)
if err != nil {
writeError(c, err)
continue
}
payloadBytes, err := json.Marshal(payloadPayload)
if err != nil {
writeError(c, err)
continue
}
_, err = c.Write(payloadBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
buf := make([]byte, 1024*1024)
nr, err := c.Read(buf)
if err != nil {
return
}
data := buf[0:nr]
var msg messages.IPCMessage
err = json.Unmarshal(data, &msg)
if err != nil {
writeError(c, err)
continue
}
if msg.Type == messages.MessageTypeForEmptyPayload(messages.PinentryApprovalResponse{}) {
getApprovalResponse := messages.ParsePayload(msg).(messages.PinentryApprovalResponse)
getApprovalReturnChan <- struct {
approved bool
err error
}{getApprovalResponse.Approved, nil}
}
2024-02-09 00:24:28 +01:00
}
}
continue
}
var responseBytes []byte
2023-07-17 03:23:26 +02:00
if action, actionFound := actions.AgentActionsRegistry.Get(msg.Type); actionFound {
callingContext := sockets.GetCallingContext(c)
2023-09-12 18:56:35 +02:00
payload, err := action(msg, cfg, vault, &callingContext)
2023-07-17 03:23:26 +02:00
if err != nil {
writeError(c, err)
continue
}
responseBytes, err = json.Marshal(payload)
if err != nil {
writeError(c, err)
continue
}
} else {
2023-09-20 03:05:44 +02:00
payload := messages.ActionResponse{
2023-07-17 03:23:26 +02:00
Success: false,
Message: "Action not found",
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
writeError(c, err)
continue
}
responseBytes = payloadBytes
}
_, err = c.Write(responseBytes)
if err != nil {
log.Error("Failed writing to socket " + err.Error())
}
}
}
type AgentState struct {
}
2023-08-21 18:37:34 +02:00
func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
2023-07-17 03:23:26 +02:00
ctx := context.Background()
2023-12-22 08:02:23 +01:00
var keyring crypto.Keyring
if runtimeConfig.UseMemguard {
keyring = crypto.NewMemguardKeyring(nil)
} else {
keyring = crypto.NewMemoryKeyring(nil)
}
2023-07-17 03:23:26 +02:00
var vault = vault.NewVault(&keyring)
2023-08-21 18:37:34 +02:00
cfg, err := config.ReadConfig(runtimeConfig)
2023-07-17 03:23:26 +02:00
if err != nil {
log.Warn("Could not read config: %s", err.Error())
2023-12-22 12:01:21 +01:00
cfg = config.DefaultConfig(runtimeConfig.UseMemguard)
2023-09-11 14:14:27 +02:00
cfg.ConfigFile.RuntimeConfig = runtimeConfig
2023-07-17 03:23:26 +02:00
cfg.WriteConfig()
}
2023-08-21 18:37:34 +02:00
cfg.ConfigFile.RuntimeConfig = runtimeConfig
if cfg.ConfigFile.RuntimeConfig.DeviceUUID != "" {
cfg.ConfigFile.DeviceUUID = cfg.ConfigFile.RuntimeConfig.DeviceUUID
}
2023-07-17 03:23:26 +02:00
if !cfg.IsLocked() {
2023-07-17 05:02:56 +02:00
log.Warn("Config is not locked. SET A PIN!!")
2023-07-17 03:23:26 +02:00
token, err := cfg.GetToken()
if err == nil {
if token.AccessToken != "" {
2023-09-20 03:32:37 +02:00
// attempt to sync every minute until successful
for {
bitwarden.RefreshToken(ctx, &cfg)
2024-01-19 12:28:01 +01:00
token, err := cfg.GetToken()
if err != nil {
log.Error("Could not get token: %s", err.Error())
}
2023-09-20 03:32:37 +02:00
userSymmetricKey, err := cfg.GetUserSymmetricKey()
if err != nil {
fmt.Println(err)
time.Sleep(60 * time.Second)
continue
}
2023-12-22 08:02:23 +01:00
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard {
protectedUserSymetricKey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userSymmetricKey)
} else {
protectedUserSymetricKey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmetricKey)
}
2023-09-20 03:32:37 +02:00
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
if err != nil {
2024-01-19 12:28:01 +01:00
log.Error("Could not sync: %s", err.Error())
notify.Notify("Goldwarden", "Could not perform initial sync", "", 0, func() {})
2023-09-20 03:32:37 +02:00
time.Sleep(60 * time.Second)
continue
} else {
break
}
2023-07-17 03:23:26 +02:00
}
}
}
}
2023-08-24 03:22:03 +02:00
processsecurity.DisableDumpable()
2023-12-28 01:04:46 +01:00
go func() {
err = processsecurity.MonitorLocks(func() {
cfg.Lock()
vault.Clear()
vault.Keyring.Lock()
2024-02-12 17:41:23 +01:00
systemauth.WipeSessions()
2023-12-28 01:04:46 +01:00
})
if err != nil {
log.Warn("Could not monitor screensaver: %s", err.Error())
}
}()
go func() {
err = processsecurity.MonitorIdle(func() {
2024-02-09 20:48:44 +01:00
cfg.Lock()
vault.Clear()
vault.Keyring.Lock()
2024-02-12 17:41:23 +01:00
systemauth.WipeSessions()
2023-12-28 01:04:46 +01:00
})
if err != nil {
log.Warn("Could not monitor idle: %s", err.Error())
}
}()
go func() {
err = notify.ListenForNotifications()
if err != nil {
log.Warn("Could not listen for notifications: %s", err.Error())
}
}()
2023-12-23 08:37:17 +01:00
2023-12-28 11:45:36 +01:00
go func() {
if !runtimeConfig.WebsocketDisabled {
for {
// polling, switch this to signal based later
if !cfg.IsLocked() && cfg.IsLoggedIn() {
bitwarden.RunWebsocketDaemon(ctx, vault, &cfg)
2023-12-28 14:48:42 +01:00
time.Sleep(60 * time.Second)
2023-12-28 11:45:36 +01:00
}
2023-12-28 14:48:42 +01:00
time.Sleep(1 * time.Second)
2023-12-28 11:45:36 +01:00
}
}
}()
2023-07-17 03:23:26 +02:00
2023-08-21 18:37:34 +02:00
if !runtimeConfig.DisableSSHAgent {
2023-12-30 18:53:01 +01:00
vaultAgent := ssh.NewVaultAgent(vault, &cfg, &runtimeConfig)
2023-08-21 13:52:06 +02:00
vaultAgent.SetUnlockRequestAction(func() bool {
err := cfg.TryUnlock(vault)
2023-07-17 03:23:26 +02:00
if err == nil {
2023-08-21 13:52:06 +02:00
token, err := cfg.GetToken()
if err == nil {
if token.AccessToken != "" {
2024-01-19 05:14:25 +01:00
gotToken := bitwarden.RefreshToken(ctx, &cfg)
if !gotToken {
log.Warn("Could not get token")
return false
2024-01-19 12:28:01 +01:00
} else {
token, err = cfg.GetToken()
if err != nil {
log.Warn("Could not get token: %s", err.Error())
return false
}
2024-01-19 05:14:25 +01:00
}
2023-08-21 13:52:06 +02:00
userSymmetricKey, err := cfg.GetUserSymmetricKey()
if err != nil {
2024-01-19 05:14:25 +01:00
log.Error("Could not get user symmetric key: %s", err.Error())
2023-08-21 13:52:06 +02:00
}
2023-12-22 08:02:23 +01:00
var protectedUserSymetricKey crypto.SymmetricEncryptionKey
if vault.Keyring.IsMemguard {
protectedUserSymetricKey, err = crypto.MemguardSymmetricEncryptionKeyFromBytes(userSymmetricKey)
} else {
protectedUserSymetricKey, err = crypto.MemorySymmetricEncryptionKeyFromBytes(userSymmetricKey)
}
2023-08-21 13:52:06 +02:00
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
if err != nil {
2024-01-19 05:14:25 +01:00
log.Error("Could not sync: %s", err.Error())
2024-01-19 12:28:01 +01:00
notify.Notify("Goldwarden", "Could not perform initial sync on ssh unlock", "", 0, func() {})
2023-08-21 13:52:06 +02:00
}
2024-01-19 05:14:25 +01:00
} else {
log.Warn("Access token is empty")
2023-07-17 03:23:26 +02:00
}
2024-01-19 05:14:25 +01:00
} else {
log.Error("Could not get token: %s", err.Error())
2023-07-17 03:23:26 +02:00
}
2023-08-21 13:52:06 +02:00
return true
2024-01-19 05:14:25 +01:00
} else {
log.Warn("Could not unlock: %s", err.Error())
2023-07-17 03:23:26 +02:00
}
2023-08-21 13:52:06 +02:00
return false
})
go vaultAgent.Serve()
}
2023-07-17 03:23:26 +02:00
go func() {
for {
time.Sleep(TokenRefreshInterval)
if !cfg.IsLocked() {
bitwarden.RefreshToken(ctx, &cfg)
}
}
}()
go func() {
for {
time.Sleep(FullSyncInterval)
if !cfg.IsLocked() {
2024-01-04 21:53:38 +01:00
bitwarden.RefreshToken(ctx, &cfg)
2023-07-17 03:23:26 +02:00
token, err := cfg.GetToken()
if err != nil {
log.Warn("Could not get token: %s", err.Error())
continue
}
2024-01-19 12:28:01 +01:00
bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, nil, false)
2023-07-17 03:23:26 +02:00
}
}
}()
if _, err := os.Stat(path); err == nil {
if err := os.Remove(path); err != nil {
return err
}
}
l, err := net.Listen("unix", path)
if err != nil {
fmt.Println("listen error", err.Error())
2023-07-17 03:23:26 +02:00
return err
}
2024-02-04 01:28:43 +01:00
defer l.Close()
2023-07-17 03:23:26 +02:00
log.Info("Agent listening on %s...", path)
2023-09-20 18:00:00 +02:00
go func() {
for {
fd, err := l.Accept()
if err != nil {
fmt.Println("accept error", err.Error())
2023-09-20 18:00:00 +02:00
}
go serveAgentSession(fd, vault, &cfg)
2023-07-17 03:23:26 +02:00
}
2023-09-20 18:00:00 +02:00
}()
2023-07-17 03:23:26 +02:00
2023-09-20 18:00:00 +02:00
mainloop()
return nil
2023-07-17 03:23:26 +02:00
}