Add more env variables

This commit is contained in:
Bernd Schoolmann 2023-08-21 18:37:34 +02:00
parent 6e3859dd43
commit 6d21d10a9e
No known key found for this signature in database
23 changed files with 336 additions and 100 deletions

View file

@ -170,9 +170,25 @@ You can bind this to a hotkey in your desktop environment (i.e i3/sway config fi
### Login with device ### Login with device
Approving other devices works out of the box and is enabled by default. If the agent is unlocked, you will be prompted Approving other devices works out of the box and is enabled by default. If the agent is unlocked, you will be prompted
to approve the device. to approve the device. If you want to log into goldwarden using another device, add the "--passwordless" parameter to the login command.
### Environment Variables
```
GOLDWARDEN_WEBSOCKET_DISABLED=true # disable websocket
GOLDWARDEN_PIN_REQUIREMENT_DISABLED=true # disable pin requirement
GOLDWARDEN_DO_NOT_PERSIST_CONFIG=true # do not persist config
GOLDWARDEN_API_URI=https://my.bitwarden.domain/api # set api uri
GOLDWARDEN_IDENTITY_URI=https://my.bitwarden.domain/identity # set identity uri
GOLDWARDEN_SINGLE_PROCESS=true # run in single process mode, i.e no daemon
GOLDWARDEN_DEVICE_UUID= # set device uuid
GOLDWARDEN_AUTH_METHOD= # set auth method (password/passwordless)
GOLDWARDEN_AUTH_USER= # set auth user
GOLDWARDEN_AUTH_PASSWORD= # set auth password
GOLDWARDEN_SILENT_LOGGING=true # disable logging
GOLDWARDEN_SYSTEM_AUTH_DISABLED=true # disable system auth (biometrics / approval)
```
### Building ### Building
To build, you will need libfido2-dev. And a go toolchain. To build, you will need libfido2-dev. And a go toolchain.
@ -204,7 +220,6 @@ When accessing a vault entry, the daemon will authenticate against a polkit poli
### Future Plans ### Future Plans
Some things that I consider adding (depending on time and personal need): Some things that I consider adding (depending on time and personal need):
- Paswordless sign in
- Regular cli managment (add, delete, update, of logins / secure notes) - Regular cli managment (add, delete, update, of logins / secure notes)
- Scripts to properly set up the policies/systemd/etc. - Scripts to properly set up the policies/systemd/etc.

View file

@ -69,6 +69,10 @@ func sync(ctx context.Context, vault *vault.Vault, cfg *config.Config) bool {
func ensureIsNotLocked(action Action) Action { func ensureIsNotLocked(action Action) Action {
return func(request ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx sockets.CallingContext) (interface{}, error) { return func(request ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx sockets.CallingContext) (interface{}, error) {
if cfg.ConfigFile.RuntimeConfig.DisablePinRequirement {
return action(request, cfg, vault, ctx)
}
if cfg.IsLocked() { if cfg.IsLocked() {
err := cfg.TryUnlock(vault) err := cfg.TryUnlock(vault)
ctx1 := context.Background() ctx1 := context.Background()

View file

@ -13,7 +13,7 @@ import (
) )
func handleLogin(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext sockets.CallingContext) (response interface{}, err error) { func handleLogin(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext sockets.CallingContext) (response interface{}, err error) {
if !cfg.HasPin() { if !cfg.HasPin() && !cfg.ConfigFile.RuntimeConfig.DisablePinRequirement {
response, err = ipc.IPCMessageFromPayload(ipc.ActionResponse{ response, err = ipc.IPCMessageFromPayload(ipc.ActionResponse{
Success: false, Success: false,
Message: "No pin set. Set a pin first!", Message: "No pin set. Set a pin first!",

View file

@ -4,7 +4,6 @@ import (
"context" "context"
"strings" "strings"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/sockets" "github.com/quexten/goldwarden/agent/sockets"
@ -12,9 +11,10 @@ import (
"github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc" "github.com/quexten/goldwarden/ipc"
"github.com/quexten/goldwarden/logging"
) )
var actionsLog = llamalog.NewLogger("Goldwarden", "Actions") var actionsLog = logging.GetLogger("Goldwarden", "Actions")
func handleAddSSH(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext sockets.CallingContext) (response interface{}, err error) { func handleAddSSH(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext sockets.CallingContext) (response interface{}, err error) {
req := msg.ParsedPayload().(ipc.CreateSSHKeyRequest) req := msg.ParsedPayload().(ipc.CreateSSHKeyRequest)

View file

@ -14,17 +14,17 @@ import (
"strings" "strings"
"time" "time"
"github.com/LlamaNite/llamalog"
"github.com/awnumar/memguard" "github.com/awnumar/memguard"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/bitwarden/twofactor" "github.com/quexten/goldwarden/agent/bitwarden/twofactor"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/logging"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
var authLog = llamalog.NewLogger("Goldwarden", "Auth") var authLog = logging.GetLogger("Goldwarden", "Auth")
type preLoginRequest struct { type preLoginRequest struct {
Email string `json:"email"` Email string `json:"email"`
@ -103,22 +103,10 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values) err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
errsc, ok := err.(*errStatusCode) errsc, ok := err.(*errStatusCode)
if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) { if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) {
var twoFactor twofactor.TwoFactorResponse loginResponseToken, err = Perform2FA(values, errsc, cfg, ctx)
if err := json.Unmarshal(errsc.body, &twoFactor); err != nil { if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", err return LoginResponseToken{}, crypto.MasterKey{}, "", err
} }
provider, token, err := twofactor.PerformSecondFactor(&twoFactor, cfg)
if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not obtain two-factor auth token: %v", err)
}
values.Set("twoFactorProvider", strconv.Itoa(int(provider)))
values.Set("twoFactorToken", string(token))
values.Set("twoFactorRemember", "1")
loginResponseToken = LoginResponseToken{}
if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values); err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via two-factor: %v", err)
}
authLog.Info("2FA login successful")
} else if err != nil && strings.Contains(err.Error(), "Captcha required.") { } else if err != nil && strings.Contains(err.Error(), "Captcha required.") {
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("captcha required, please login via the web interface") return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("captcha required, please login via the web interface")
@ -177,8 +165,17 @@ func LoginWithDevice(ctx context.Context, email string, cfg *config.Config, vaul
var loginResponseToken LoginResponseToken var loginResponseToken LoginResponseToken
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values) err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
if err != nil { errsc, ok := err.(*errStatusCode)
return LoginResponseToken{}, crypto.MasterKey{}, "", err if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) {
loginResponseToken, err = Perform2FA(values, errsc, cfg, ctx)
if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", err
}
} else if err != nil && strings.Contains(err.Error(), "Captcha required.") {
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("captcha required, please login via the web interface")
} else if err != nil {
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not login via password: %v", err)
} }
return loginResponseToken, crypto.MasterKeyFromBytes(masterKey), string(masterPasswordHash), nil return loginResponseToken, crypto.MasterKeyFromBytes(masterKey), string(masterPasswordHash), nil
} }
@ -218,6 +215,26 @@ func RefreshToken(ctx context.Context, cfg *config.Config) bool {
return true return true
} }
func Perform2FA(values url.Values, errsc *errStatusCode, cfg *config.Config, ctx context.Context) (LoginResponseToken, error) {
var twoFactor twofactor.TwoFactorResponse
if err := json.Unmarshal(errsc.body, &twoFactor); err != nil {
return LoginResponseToken{}, err
}
provider, token, err := twofactor.PerformSecondFactor(&twoFactor, cfg)
if err != nil {
return LoginResponseToken{}, fmt.Errorf("could not obtain two-factor auth token: %v", err)
}
values.Set("twoFactorProvider", strconv.Itoa(int(provider)))
values.Set("twoFactorToken", string(token))
values.Set("twoFactorRemember", "1")
loginResponseToken := LoginResponseToken{}
if err := authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values); err != nil {
return LoginResponseToken{}, fmt.Errorf("could not login via two-factor: %v", err)
}
authLog.Info("2FA login successful")
return loginResponseToken, nil
}
func urlValues(pairs ...string) url.Values { func urlValues(pairs ...string) url.Values {
if len(pairs)%2 != 0 { if len(pairs)%2 != 0 {
panic("pairs must be of even length") panic("pairs must be of even length")

View file

@ -6,14 +6,14 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
"github.com/quexten/goldwarden/agent/bitwarden/models" "github.com/quexten/goldwarden/agent/bitwarden/models"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/logging"
) )
var log = llamalog.NewLogger("Goldwarden", "Bitwarden API") var log = logging.GetLogger("Goldwarden", "Bitwarden API")
const path = "/.cache/goldwarden-vault.json" const path = "/.cache/goldwarden-vault.json"

View file

@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/logging"
) )
var twofactorLog = llamalog.NewLogger("Goldwarden", "TwoFactor") var twofactorLog = logging.GetLogger("Goldwarden", "TwoFactor")
func PerformSecondFactor(resp *TwoFactorResponse, cfg *config.Config) (TwoFactorProvider, []byte, error) { func PerformSecondFactor(resp *TwoFactorResponse, cfg *config.Config) (TwoFactorProvider, []byte, error) {
if resp.TwoFactorProviders2[WebAuthn] != nil { if resp.TwoFactorProviders2[WebAuthn] != nil {

View file

@ -8,17 +8,17 @@ import (
"os/signal" "os/signal"
"time" "time"
"github.com/LlamaNite/llamalog"
"github.com/awnumar/memguard" "github.com/awnumar/memguard"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/quexten/goldwarden/agent/bitwarden/models" "github.com/quexten/goldwarden/agent/bitwarden/models"
"github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/logging"
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
) )
var websocketLog = llamalog.NewLogger("Goldwarden", "Websocket") var websocketLog = logging.GetLogger("Goldwarden", "Websocket")
type NotificationMessageType int64 type NotificationMessageType int64

View file

@ -21,12 +21,29 @@ import (
) )
const ( const (
KDFIterations = 2 KDFIterations = 2
KDFMemory = 2 * 1024 * 1024 KDFMemory = 2 * 1024 * 1024
KDFThreads = 8 KDFThreads = 8
ConfigPath = "/.config/goldwarden.json" DefaultConfigPath = "~/.config/goldwarden.json"
) )
type RuntimeConfig struct {
DisableAuth bool
DisablePinRequirement bool
AuthMethod string
DoNotPersistConfig bool
ConfigDirectory string
DisableSSHAgent bool
WebsocketDisabled bool
ApiURI string
IdentityURI string
SingleProcess bool
DeviceUUID string
User string
Password string
Pin string
}
type ConfigFile struct { type ConfigFile struct {
IdentityUrl string IdentityUrl string
ApiUrl string ApiUrl string
@ -36,6 +53,7 @@ type ConfigFile struct {
EncryptedUserSymmetricKey string EncryptedUserSymmetricKey string
EncryptedMasterPasswordHash string EncryptedMasterPasswordHash string
EncryptedMasterKey string EncryptedMasterKey string
RuntimeConfig RuntimeConfig `json:"-"`
} }
type LoginToken struct { type LoginToken struct {
@ -65,13 +83,14 @@ func DefaultConfig() Config {
EncryptedUserSymmetricKey: "", EncryptedUserSymmetricKey: "",
EncryptedMasterPasswordHash: "", EncryptedMasterPasswordHash: "",
EncryptedMasterKey: "", EncryptedMasterKey: "",
RuntimeConfig: RuntimeConfig{},
}, },
sync.Mutex{}, sync.Mutex{},
} }
} }
func (c *Config) IsLocked() bool { func (c *Config) IsLocked() bool {
return c.key == nil return c.key.EqualTo(make([]byte, 32)) && c.HasPin()
} }
func (c *Config) IsLoggedIn() bool { func (c *Config) IsLoggedIn() bool {
@ -105,8 +124,7 @@ func (c *Config) Lock() {
if c.IsLocked() { if c.IsLocked() {
return return
} }
c.key.Destroy() c.key.Wipe()
c.key = nil
} }
func (c *Config) Purge() { func (c *Config) Purge() {
@ -315,6 +333,10 @@ func (c *Config) decryptString(data string) (string, error) {
} }
func (config *Config) WriteConfig() error { func (config *Config) WriteConfig() error {
if config.ConfigFile.RuntimeConfig.DoNotPersistConfig {
return nil
}
config.mu.Lock() config.mu.Lock()
defer config.mu.Unlock() defer config.mu.Unlock()
@ -323,13 +345,9 @@ func (config *Config) WriteConfig() error {
return err return err
} }
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
// write to disk // write to disk
os.Remove(home + ConfigPath) os.Remove(config.ConfigFile.RuntimeConfig.ConfigDirectory)
file, err := os.OpenFile(home+ConfigPath, os.O_CREATE|os.O_WRONLY, 0600) file, err := os.OpenFile(config.ConfigFile.RuntimeConfig.ConfigDirectory, os.O_CREATE|os.O_WRONLY, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -342,14 +360,13 @@ func (config *Config) WriteConfig() error {
return nil return nil
} }
func ReadConfig() (Config, error) { func ReadConfig(rtCfg RuntimeConfig) (Config, error) {
home, err := os.UserHomeDir() file, err := os.Open(rtCfg.ConfigDirectory)
if err != nil { if err != nil {
panic(err) return Config{
} key: memguard.NewBuffer(32),
file, err := os.Open(home + ConfigPath) ConfigFile: ConfigFile{},
if err != nil { }, err
return Config{ConfigFile: ConfigFile{}}, err
} }
defer file.Close() defer file.Close()
@ -357,12 +374,21 @@ func ReadConfig() (Config, error) {
config := ConfigFile{} config := ConfigFile{}
err = decoder.Decode(&config) err = decoder.Decode(&config)
if err != nil { if err != nil {
return Config{ConfigFile: ConfigFile{}}, err return Config{
key: memguard.NewBuffer(32),
ConfigFile: ConfigFile{},
}, err
} }
if config.ConfigKeyHash == "" { if config.ConfigKeyHash == "" {
return Config{ConfigFile: config, key: memguard.NewBuffer(32)}, nil return Config{
key: memguard.NewBuffer(32),
ConfigFile: config,
}, nil
} }
return Config{ConfigFile: config}, nil return Config{
key: memguard.NewBuffer(32),
ConfigFile: config,
}, nil
} }
func (cfg *Config) TryUnlock(vault *vault.Vault) error { func (cfg *Config) TryUnlock(vault *vault.Vault) error {

View file

@ -8,15 +8,15 @@ import (
"net" "net"
"os" "os"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/sockets" "github.com/quexten/goldwarden/agent/sockets"
"github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/systemauth"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/logging"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/agent"
) )
var log = llamalog.NewLogger("Goldwarden", "SSH") var log = logging.GetLogger("Goldwarden", "SSH")
type vaultAgent struct { type vaultAgent struct {
vault *vault.Vault vault *vault.Vault

View file

@ -2,13 +2,10 @@ package systemauth
import ( import (
"errors" "errors"
"os"
"github.com/twpayne/go-pinentry" "github.com/twpayne/go-pinentry"
) )
var authDisabled = false
func GetPassword(title string, description string) (string, error) { func GetPassword(title string, description string) (string, error) {
client, err := pinentry.NewClient( client, err := pinentry.NewClient(
pinentry.WithBinaryNameFromGnuPGAgentConf(), pinentry.WithBinaryNameFromGnuPGAgentConf(),
@ -40,8 +37,7 @@ func GetPassword(title string, description string) (string, error) {
} }
func GetApproval(title string, description string) (bool, error) { func GetApproval(title string, description string) (bool, error) {
if authDisabled { if systemAuthDisabled {
log.Info("Skipping approval because system auth is disabled")
return true, nil return true, nil
} }
@ -70,10 +66,3 @@ func GetApproval(title string, description string) (bool, error) {
return true, nil return true, nil
} }
} }
func init() {
envAuthDisabled := os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED")
if envAuthDisabled == "true" {
authDisabled = true
}
}

View file

@ -1,11 +1,11 @@
package systemauth package systemauth
import ( import (
"github.com/LlamaNite/llamalog"
"github.com/amenzhinsky/go-polkit" "github.com/amenzhinsky/go-polkit"
"github.com/quexten/goldwarden/logging"
) )
var log = llamalog.NewLogger("Goldwarden", "Systemauth") var log = logging.GetLogger("Goldwarden", "Systemauth")
type Approval string type Approval string
@ -75,11 +75,12 @@ func (a Approval) String() string {
} }
func CheckBiometrics(approvalType Approval) bool { func CheckBiometrics(approvalType Approval) bool {
log.Info("Checking biometrics for %s", approvalType.String()) if systemAuthDisabled {
if authDisabled {
return true return true
} }
log.Info("Checking biometrics for %s", approvalType.String())
authority, err := polkit.NewAuthority() authority, err := polkit.NewAuthority()
if err != nil { if err != nil {
return false return false

View file

@ -0,0 +1,11 @@
package systemauth
import "os"
var systemAuthDisabled = false
func init() {
if os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") == "true" {
systemAuthDisabled = true
}
}

View file

@ -8,7 +8,6 @@ import (
"os" "os"
"time" "time"
"github.com/LlamaNite/llamalog"
"github.com/quexten/goldwarden/agent/actions" "github.com/quexten/goldwarden/agent/actions"
"github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/bitwarden"
"github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/crypto"
@ -17,6 +16,7 @@ import (
"github.com/quexten/goldwarden/agent/ssh" "github.com/quexten/goldwarden/agent/ssh"
"github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/agent/vault"
"github.com/quexten/goldwarden/ipc" "github.com/quexten/goldwarden/ipc"
"github.com/quexten/goldwarden/logging"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -25,7 +25,7 @@ const (
TokenRefreshInterval = 30 * time.Minute TokenRefreshInterval = 30 * time.Minute
) )
var log = llamalog.NewLogger("Goldwarden", "Agent") var log = logging.GetLogger("Goldwarden", "Agent")
func writeError(c net.Conn, errMsg error) error { func writeError(c net.Conn, errMsg error) error {
payload := ipc.ActionResponse{ payload := ipc.ActionResponse{
@ -102,17 +102,28 @@ type AgentState struct {
config *config.ConfigFile config *config.ConfigFile
} }
func StartUnixAgent(path string, websocket bool, sshAgent bool) error { func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error {
ctx := context.Background() ctx := context.Background()
// check if exists // check if exists
keyring := crypto.NewKeyring(nil) keyring := crypto.NewKeyring(nil)
var vault = vault.NewVault(&keyring) var vault = vault.NewVault(&keyring)
cfg, err := config.ReadConfig() cfg, err := config.ReadConfig(runtimeConfig)
if err != nil { if err != nil {
var cfg = config.DefaultConfig() var cfg = config.DefaultConfig()
cfg.WriteConfig() cfg.WriteConfig()
} }
cfg.ConfigFile.RuntimeConfig = runtimeConfig
if cfg.ConfigFile.RuntimeConfig.ApiURI != "" {
cfg.ConfigFile.ApiUrl = cfg.ConfigFile.RuntimeConfig.ApiURI
}
if cfg.ConfigFile.RuntimeConfig.IdentityURI != "" {
cfg.ConfigFile.IdentityUrl = cfg.ConfigFile.RuntimeConfig.IdentityURI
}
if cfg.ConfigFile.RuntimeConfig.DeviceUUID != "" {
cfg.ConfigFile.DeviceUUID = cfg.ConfigFile.RuntimeConfig.DeviceUUID
}
if !cfg.IsLocked() { if !cfg.IsLocked() {
log.Warn("Config is not locked. SET A PIN!!") log.Warn("Config is not locked. SET A PIN!!")
token, err := cfg.GetToken() token, err := cfg.GetToken()
@ -134,11 +145,11 @@ func StartUnixAgent(path string, websocket bool, sshAgent bool) error {
} }
disableDumpable() disableDumpable()
if websocket { if !runtimeConfig.WebsocketDisabled {
go bitwarden.RunWebsocketDaemon(ctx, vault, &cfg) go bitwarden.RunWebsocketDaemon(ctx, vault, &cfg)
} }
if sshAgent { if !runtimeConfig.DisableSSHAgent {
vaultAgent := ssh.NewVaultAgent(vault) vaultAgent := ssh.NewVaultAgent(vault)
vaultAgent.SetUnlockRequestAction(func() bool { vaultAgent.SetUnlockRequestAction(func() bool {
err := cfg.TryUnlock(vault) err := cfg.TryUnlock(vault)

View file

@ -70,18 +70,29 @@ func serveVirtualAgent(recv chan []byte, send chan []byte, ctx context.Context,
} }
} }
func StartVirtualAgent() (chan []byte, chan []byte) { func StartVirtualAgent(runtimeConfig config.RuntimeConfig) (chan []byte, chan []byte) {
ctx := context.Background() ctx := context.Background()
// check if exists // check if exists
keyring := crypto.NewKeyring(nil) keyring := crypto.NewKeyring(nil)
var vault = vault.NewVault(&keyring) var vault = vault.NewVault(&keyring)
cfg, err := config.ReadConfig() cfg, err := config.ReadConfig(runtimeConfig)
if err != nil { if err != nil {
var cfg = config.DefaultConfig() var cfg = config.DefaultConfig()
cfg.WriteConfig() cfg.WriteConfig()
} }
if !cfg.IsLocked() { cfg.ConfigFile.RuntimeConfig = runtimeConfig
if cfg.ConfigFile.RuntimeConfig.ApiURI != "" {
cfg.ConfigFile.ApiUrl = cfg.ConfigFile.RuntimeConfig.ApiURI
}
if cfg.ConfigFile.RuntimeConfig.IdentityURI != "" {
cfg.ConfigFile.IdentityUrl = cfg.ConfigFile.RuntimeConfig.IdentityURI
}
if cfg.ConfigFile.RuntimeConfig.DeviceUUID != "" {
cfg.ConfigFile.DeviceUUID = cfg.ConfigFile.RuntimeConfig.DeviceUUID
}
if !cfg.IsLocked() && !cfg.ConfigFile.RuntimeConfig.DoNotPersistConfig {
log.Warn("Config is not locked. SET A PIN!!") log.Warn("Config is not locked. SET A PIN!!")
token, err := cfg.GetToken() token, err := cfg.GetToken()
if err == nil { if err == nil {

View file

@ -11,7 +11,7 @@ import (
"github.com/quexten/goldwarden/ipc" "github.com/quexten/goldwarden/ipc"
) )
func GetLoginByUUID(uuid string) (ipc.DecryptedLoginCipher, error) { func GetLoginByUUID(uuid string, client client.Client) (ipc.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(ipc.GetLoginRequest{ resp, err := client.SendToAgent(ipc.GetLoginRequest{
UUID: uuid, UUID: uuid,
}) })
@ -31,7 +31,7 @@ func GetLoginByUUID(uuid string) (ipc.DecryptedLoginCipher, error) {
} }
} }
func ListLogins() ([]ipc.DecryptedLoginCipher, error) { func ListLogins(client client.Client) ([]ipc.DecryptedLoginCipher, error) {
resp, err := client.SendToAgent(ipc.ListLoginsRequest{}) resp, err := client.SendToAgent(ipc.ListLoginsRequest{})
if err != nil { if err != nil {
return []ipc.DecryptedLoginCipher{}, err return []ipc.DecryptedLoginCipher{}, err
@ -49,8 +49,8 @@ func ListLogins() ([]ipc.DecryptedLoginCipher, error) {
} }
} }
func Run(layout string, useCopyPaste bool) { func Run(layout string, useCopyPaste bool, client client.Client) {
logins, err := ListLogins() logins, err := ListLogins(client)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -65,7 +65,7 @@ func Run(layout string, useCopyPaste bool) {
} }
RunAutofill(autofillEntries, func(uuid string, c chan bool) { RunAutofill(autofillEntries, func(uuid string, c chan bool) {
login, err := GetLoginByUUID(uuid) login, err := GetLoginByUUID(uuid, client)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -1,4 +1,4 @@
//go:build autofill //go:build !noautofill
package cmd package cmd
@ -14,7 +14,7 @@ var autofillCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
layout := cmd.Flag("layout").Value.String() layout := cmd.Flag("layout").Value.String()
useCopyPaste, _ := cmd.Flags().GetBool("use-copy-paste") useCopyPaste, _ := cmd.Flags().GetBool("use-copy-paste")
autofill.Run(layout, useCopyPaste) autofill.Run(layout, useCopyPaste, commandClient)
}, },
} }

View file

@ -15,12 +15,13 @@ var daemonizeCmd = &cobra.Command{
Long: `Starts the agent as a daemon. The agent will run in the background and will Long: `Starts the agent as a daemon. The agent will run in the background and will
run in the background until it is stopped.`, run in the background until it is stopped.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
websocketDisabled := os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true" websocketDisabled := runtimeConfig.WebsocketDisabled
sshDisabled := runtimeConfig.DisableSSHAgent
if websocketDisabled { if websocketDisabled {
println("Websocket disabled") println("Websocket disabled")
} }
sshDisabled := os.Getenv("GOLDWARDEN_SSH_DISABLED") == "true"
if sshDisabled { if sshDisabled {
println("SSH agent disabled") println("SSH agent disabled")
} }
@ -35,7 +36,7 @@ var daemonizeCmd = &cobra.Command{
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = agent.StartUnixAgent(home+"/.goldwarden.sock", websocketDisabled, sshDisabled) err = agent.StartUnixAgent(home+"/.goldwarden.sock", runtimeConfig)
if err != nil { if err != nil {
panic(err) panic(err)
} }

62
cmd/logins.go Normal file
View file

@ -0,0 +1,62 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/quexten/goldwarden/ipc"
"github.com/spf13/cobra"
)
var baseLoginCmd = &cobra.Command{
Use: "logins",
Short: "Commands for managing logins.",
Long: `Commands for managing logins.`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
var getLoginCmd = &cobra.Command{
Use: "get",
Short: "Gets a login in your vault",
Long: `Gets a login in your vault.`,
Run: func(cmd *cobra.Command, args []string) {
loginIfRequired()
uuid, _ := cmd.Flags().GetString("uuid")
name, _ := cmd.Flags().GetString("name")
username, _ := cmd.Flags().GetString("username")
resp, err := commandClient.SendToAgent(ipc.GetLoginRequest{
Name: name,
Username: username,
UUID: uuid,
})
if err != nil {
println("Error: " + err.Error())
println("Is the daemon running?")
return
}
switch resp.(type) {
case ipc.GetLoginResponse:
response := resp.(ipc.GetLoginResponse)
fmt.Println(response.Result.Password)
break
case ipc.ActionResponse:
println("Error: " + resp.(ipc.ActionResponse).Message)
return
}
},
}
func init() {
rootCmd.AddCommand(baseLoginCmd)
baseLoginCmd.AddCommand(getLoginCmd)
getLoginCmd.PersistentFlags().String("name", "", "")
getLoginCmd.PersistentFlags().String("username", "", "")
getLoginCmd.PersistentFlags().String("uuid", "", "")
}

View file

@ -4,11 +4,14 @@ import (
"os" "os"
"github.com/quexten/goldwarden/agent" "github.com/quexten/goldwarden/agent"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/client" "github.com/quexten/goldwarden/client"
"github.com/quexten/goldwarden/ipc"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var commandClient client.Client var commandClient client.Client
var runtimeConfig config.RuntimeConfig
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "goldwarden", Use: "goldwarden",
@ -18,7 +21,17 @@ var rootCmd = &cobra.Command{
biometric unlock, and more.`, biometric unlock, and more.`,
} }
func Execute() { func Execute(cfg config.RuntimeConfig) {
runtimeConfig = cfg
goldwardenSingleProcess := os.Getenv("GOLDWARDEN_SINGLE_PROCESS")
if goldwardenSingleProcess == "true" {
recv, send := agent.StartVirtualAgent(runtimeConfig)
commandClient = client.NewVirtualClient(send, recv)
} else {
commandClient = client.NewUnixSocketClient()
}
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
@ -26,13 +39,23 @@ func Execute() {
} }
func init() { func init() {
goldwardenSingleProcess := os.Getenv("GOLDWARDEN_SINGLE_PROCESS")
if goldwardenSingleProcess == "true" {
recv, send := agent.StartVirtualAgent()
commandClient = client.NewVirtualClient(send, recv)
} else {
commandClient = client.NewUnixSocketClient()
}
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
func loginIfRequired() error {
var err error
if runtimeConfig.AuthMethod == "password" {
_, err = commandClient.SendToAgent(ipc.DoLoginRequest{
Email: runtimeConfig.User,
Password: runtimeConfig.Password,
})
} else if runtimeConfig.AuthMethod == "passwordless" {
_, err = commandClient.SendToAgent(ipc.DoLoginRequest{
Email: runtimeConfig.User,
Passwordless: true,
})
}
return err
}

View file

@ -27,6 +27,8 @@ var sshAddCmd = &cobra.Command{
Long: `Runs a command with environment variables from your vault. Long: `Runs a command with environment variables from your vault.
The variables are stored as a secure note. Consult the documentation for more information.`, The variables are stored as a secure note. Consult the documentation for more information.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
loginIfRequired()
name, _ := cmd.Flags().GetString("name") name, _ := cmd.Flags().GetString("name")
copyToClipboard, _ := cmd.Flags().GetBool("clipboard") copyToClipboard, _ := cmd.Flags().GetBool("clipboard")
@ -60,6 +62,8 @@ var listSSHCmd = &cobra.Command{
Short: "Lists all SSH keys in your vault", Short: "Lists all SSH keys in your vault",
Long: `Lists all SSH keys in your vault.`, Long: `Lists all SSH keys in your vault.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
loginIfRequired()
result, err := commandClient.SendToAgent(ipc.GetSSHKeysRequest{}) result, err := commandClient.SendToAgent(ipc.GetSSHKeysRequest{})
if err != nil { if err != nil {
println("Error: " + err.Error()) println("Error: " + err.Error())

22
logging/llamalogger.go Normal file
View file

@ -0,0 +1,22 @@
package logging
import (
"os"
"github.com/LlamaNite/llamalog"
)
type SilentWriter struct {
}
func (w SilentWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func GetLogger(packageDir ...string) *llamalog.Logger {
if os.Getenv("GOLDWARDEN_SILENT_LOGGING") == "true" {
return llamalog.NewLoggerFromWriter(SilentWriter{}, packageDir...)
} else {
return llamalog.NewLogger(packageDir...)
}
}

43
main.go
View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/quexten/goldwarden/agent/config"
"github.com/quexten/goldwarden/browserbiometrics" "github.com/quexten/goldwarden/browserbiometrics"
"github.com/quexten/goldwarden/cmd" "github.com/quexten/goldwarden/cmd"
) )
@ -16,10 +17,48 @@ func main() {
return return
} }
if !cmd.IsPolkitSetup() { var configPath string
if path, found := os.LookupEnv("GOLDWARDEN_CONFIG_DIRECTORY"); found {
configPath = path
} else {
configPath = config.DefaultConfigPath
}
userHome, _ := os.UserHomeDir()
configPath = strings.ReplaceAll(configPath, "~", userHome)
runtimeConfig := config.RuntimeConfig{
DisableAuth: os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true",
DisablePinRequirement: os.Getenv("GOLDWARDEN_PIN_REQUIREMENT_DISABLED") == "true",
DoNotPersistConfig: os.Getenv("GOLDWARDEN_DO_NOT_PERSIST_CONFIG") == "true",
ApiURI: os.Getenv("GOLDWARDEN_API_URI"),
IdentityURI: os.Getenv("GOLDWARDEN_IDENTITY_URI"),
SingleProcess: os.Getenv("GOLDWARDEN_SINGLE_PROCESS") == "true",
DeviceUUID: os.Getenv("GOLDWARDEN_DEVICE_UUID"),
AuthMethod: os.Getenv("GOLDWARDEN_AUTH_METHOD"),
User: os.Getenv("GOLDWARDEN_AUTH_USER"),
Password: os.Getenv("GOLDWARDEN_AUTH_PASSWORD"),
Pin: os.Getenv("GOLDWARDEN_PIN"),
ConfigDirectory: configPath,
}
if runtimeConfig.SingleProcess {
runtimeConfig.DisablePinRequirement = true
runtimeConfig.DisableAuth = true
}
if runtimeConfig.DisablePinRequirement {
runtimeConfig.DoNotPersistConfig = true
}
if runtimeConfig.DisableAuth {
os.Setenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED", "true")
}
if !cmd.IsPolkitSetup() && !runtimeConfig.DisableAuth {
fmt.Println("Polkit is not setup. Run 'goldwarden setup polkit' to set it up.") fmt.Println("Polkit is not setup. Run 'goldwarden setup polkit' to set it up.")
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
} }
cmd.Execute() cmd.Execute(runtimeConfig)
} }