From 6d21d10a9effaf7163f414d2c594da4f4e84bd7d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 21 Aug 2023 18:37:34 +0200 Subject: [PATCH] Add more env variables --- Readme.md | 19 ++++++- agent/actions/actions.go | 4 ++ agent/actions/login.go | 2 +- agent/actions/ssh.go | 4 +- agent/bitwarden/auth.go | 53 ++++++++++++------- agent/bitwarden/sync.go | 4 +- agent/bitwarden/twofactor/twofactor.go | 4 +- agent/bitwarden/websocket.go | 4 +- agent/config/config.go | 72 ++++++++++++++++++-------- agent/ssh/ssh.go | 4 +- agent/systemauth/pinentry.go | 13 +---- agent/systemauth/polkitbiometrics.go | 9 ++-- agent/systemauth/systemauth.go | 11 ++++ agent/unixsocketagent.go | 23 +++++--- agent/virtualagent.go | 17 ++++-- autofill/autofill.go | 10 ++-- cmd/autofill.go | 4 +- cmd/daemonize.go | 7 +-- cmd/logins.go | 62 ++++++++++++++++++++++ cmd/root.go | 41 +++++++++++---- cmd/ssh.go | 4 ++ logging/llamalogger.go | 22 ++++++++ main.go | 43 ++++++++++++++- 23 files changed, 336 insertions(+), 100 deletions(-) create mode 100644 agent/systemauth/systemauth.go create mode 100644 cmd/logins.go create mode 100644 logging/llamalogger.go diff --git a/Readme.md b/Readme.md index 26bf5d4..12a0263 100644 --- a/Readme.md +++ b/Readme.md @@ -170,9 +170,25 @@ You can bind this to a hotkey in your desktop environment (i.e i3/sway config fi ### 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 -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 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 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) - Scripts to properly set up the policies/systemd/etc. diff --git a/agent/actions/actions.go b/agent/actions/actions.go index 1d9e83c..132d5ae 100644 --- a/agent/actions/actions.go +++ b/agent/actions/actions.go @@ -69,6 +69,10 @@ func sync(ctx context.Context, vault *vault.Vault, cfg *config.Config) bool { func ensureIsNotLocked(action Action) Action { 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() { err := cfg.TryUnlock(vault) ctx1 := context.Background() diff --git a/agent/actions/login.go b/agent/actions/login.go index 903918f..cf8a0e2 100644 --- a/agent/actions/login.go +++ b/agent/actions/login.go @@ -13,7 +13,7 @@ import ( ) 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{ Success: false, Message: "No pin set. Set a pin first!", diff --git a/agent/actions/ssh.go b/agent/actions/ssh.go index b4890cb..7eb4c83 100644 --- a/agent/actions/ssh.go +++ b/agent/actions/ssh.go @@ -4,7 +4,6 @@ import ( "context" "strings" - "github.com/LlamaNite/llamalog" "github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/sockets" @@ -12,9 +11,10 @@ import ( "github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/vault" "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) { req := msg.ParsedPayload().(ipc.CreateSSHKeyRequest) diff --git a/agent/bitwarden/auth.go b/agent/bitwarden/auth.go index 23f07fc..2bb07a0 100644 --- a/agent/bitwarden/auth.go +++ b/agent/bitwarden/auth.go @@ -14,17 +14,17 @@ import ( "strings" "time" - "github.com/LlamaNite/llamalog" "github.com/awnumar/memguard" "github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/twofactor" "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/vault" + "github.com/quexten/goldwarden/logging" "golang.org/x/crypto/pbkdf2" ) -var authLog = llamalog.NewLogger("Goldwarden", "Auth") +var authLog = logging.GetLogger("Goldwarden", "Auth") type preLoginRequest struct { 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) errsc, ok := err.(*errStatusCode) if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) { - var twoFactor twofactor.TwoFactorResponse - if err := json.Unmarshal(errsc.body, &twoFactor); err != nil { + loginResponseToken, err = Perform2FA(values, errsc, cfg, ctx) + if err != nil { 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.") { 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 err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values) - if err != nil { - return LoginResponseToken{}, crypto.MasterKey{}, "", err + errsc, ok := err.(*errStatusCode) + 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 } @@ -218,6 +215,26 @@ func RefreshToken(ctx context.Context, cfg *config.Config) bool { 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 { if len(pairs)%2 != 0 { panic("pairs must be of even length") diff --git a/agent/bitwarden/sync.go b/agent/bitwarden/sync.go index 25455ab..17cc978 100644 --- a/agent/bitwarden/sync.go +++ b/agent/bitwarden/sync.go @@ -6,14 +6,14 @@ import ( "fmt" "os" - "github.com/LlamaNite/llamalog" "github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/bitwarden/models" "github.com/quexten/goldwarden/agent/config" "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" diff --git a/agent/bitwarden/twofactor/twofactor.go b/agent/bitwarden/twofactor/twofactor.go index 1a0efd7..1a34de1 100644 --- a/agent/bitwarden/twofactor/twofactor.go +++ b/agent/bitwarden/twofactor/twofactor.go @@ -5,12 +5,12 @@ import ( "fmt" "strconv" - "github.com/LlamaNite/llamalog" "github.com/quexten/goldwarden/agent/config" "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) { if resp.TwoFactorProviders2[WebAuthn] != nil { diff --git a/agent/bitwarden/websocket.go b/agent/bitwarden/websocket.go index 56561d0..a50d76e 100644 --- a/agent/bitwarden/websocket.go +++ b/agent/bitwarden/websocket.go @@ -8,17 +8,17 @@ import ( "os/signal" "time" - "github.com/LlamaNite/llamalog" "github.com/awnumar/memguard" "github.com/gorilla/websocket" "github.com/quexten/goldwarden/agent/bitwarden/models" "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/vault" + "github.com/quexten/goldwarden/logging" "github.com/vmihailenco/msgpack/v5" ) -var websocketLog = llamalog.NewLogger("Goldwarden", "Websocket") +var websocketLog = logging.GetLogger("Goldwarden", "Websocket") type NotificationMessageType int64 diff --git a/agent/config/config.go b/agent/config/config.go index 18f92e6..2ac6293 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -21,12 +21,29 @@ import ( ) const ( - KDFIterations = 2 - KDFMemory = 2 * 1024 * 1024 - KDFThreads = 8 - ConfigPath = "/.config/goldwarden.json" + KDFIterations = 2 + KDFMemory = 2 * 1024 * 1024 + KDFThreads = 8 + 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 { IdentityUrl string ApiUrl string @@ -36,6 +53,7 @@ type ConfigFile struct { EncryptedUserSymmetricKey string EncryptedMasterPasswordHash string EncryptedMasterKey string + RuntimeConfig RuntimeConfig `json:"-"` } type LoginToken struct { @@ -65,13 +83,14 @@ func DefaultConfig() Config { EncryptedUserSymmetricKey: "", EncryptedMasterPasswordHash: "", EncryptedMasterKey: "", + RuntimeConfig: RuntimeConfig{}, }, sync.Mutex{}, } } func (c *Config) IsLocked() bool { - return c.key == nil + return c.key.EqualTo(make([]byte, 32)) && c.HasPin() } func (c *Config) IsLoggedIn() bool { @@ -105,8 +124,7 @@ func (c *Config) Lock() { if c.IsLocked() { return } - c.key.Destroy() - c.key = nil + c.key.Wipe() } func (c *Config) Purge() { @@ -315,6 +333,10 @@ func (c *Config) decryptString(data string) (string, error) { } func (config *Config) WriteConfig() error { + if config.ConfigFile.RuntimeConfig.DoNotPersistConfig { + return nil + } + config.mu.Lock() defer config.mu.Unlock() @@ -323,13 +345,9 @@ func (config *Config) WriteConfig() error { return err } - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } // write to disk - os.Remove(home + ConfigPath) - file, err := os.OpenFile(home+ConfigPath, os.O_CREATE|os.O_WRONLY, 0600) + os.Remove(config.ConfigFile.RuntimeConfig.ConfigDirectory) + file, err := os.OpenFile(config.ConfigFile.RuntimeConfig.ConfigDirectory, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return err } @@ -342,14 +360,13 @@ func (config *Config) WriteConfig() error { return nil } -func ReadConfig() (Config, error) { - home, err := os.UserHomeDir() +func ReadConfig(rtCfg RuntimeConfig) (Config, error) { + file, err := os.Open(rtCfg.ConfigDirectory) if err != nil { - panic(err) - } - file, err := os.Open(home + ConfigPath) - if err != nil { - return Config{ConfigFile: ConfigFile{}}, err + return Config{ + key: memguard.NewBuffer(32), + ConfigFile: ConfigFile{}, + }, err } defer file.Close() @@ -357,12 +374,21 @@ func ReadConfig() (Config, error) { config := ConfigFile{} err = decoder.Decode(&config) if err != nil { - return Config{ConfigFile: ConfigFile{}}, err + return Config{ + key: memguard.NewBuffer(32), + ConfigFile: ConfigFile{}, + }, err } 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 { diff --git a/agent/ssh/ssh.go b/agent/ssh/ssh.go index 91cadcc..c2c33d2 100644 --- a/agent/ssh/ssh.go +++ b/agent/ssh/ssh.go @@ -8,15 +8,15 @@ import ( "net" "os" - "github.com/LlamaNite/llamalog" "github.com/quexten/goldwarden/agent/sockets" "github.com/quexten/goldwarden/agent/systemauth" "github.com/quexten/goldwarden/agent/vault" + "github.com/quexten/goldwarden/logging" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) -var log = llamalog.NewLogger("Goldwarden", "SSH") +var log = logging.GetLogger("Goldwarden", "SSH") type vaultAgent struct { vault *vault.Vault diff --git a/agent/systemauth/pinentry.go b/agent/systemauth/pinentry.go index feebcb5..26096b8 100644 --- a/agent/systemauth/pinentry.go +++ b/agent/systemauth/pinentry.go @@ -2,13 +2,10 @@ package systemauth import ( "errors" - "os" "github.com/twpayne/go-pinentry" ) -var authDisabled = false - func GetPassword(title string, description string) (string, error) { client, err := pinentry.NewClient( pinentry.WithBinaryNameFromGnuPGAgentConf(), @@ -40,8 +37,7 @@ func GetPassword(title string, description string) (string, error) { } func GetApproval(title string, description string) (bool, error) { - if authDisabled { - log.Info("Skipping approval because system auth is disabled") + if systemAuthDisabled { return true, nil } @@ -70,10 +66,3 @@ func GetApproval(title string, description string) (bool, error) { return true, nil } } - -func init() { - envAuthDisabled := os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") - if envAuthDisabled == "true" { - authDisabled = true - } -} diff --git a/agent/systemauth/polkitbiometrics.go b/agent/systemauth/polkitbiometrics.go index 9a31392..2f4350e 100644 --- a/agent/systemauth/polkitbiometrics.go +++ b/agent/systemauth/polkitbiometrics.go @@ -1,11 +1,11 @@ package systemauth import ( - "github.com/LlamaNite/llamalog" "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 @@ -75,11 +75,12 @@ func (a Approval) String() string { } func CheckBiometrics(approvalType Approval) bool { - log.Info("Checking biometrics for %s", approvalType.String()) - if authDisabled { + if systemAuthDisabled { return true } + log.Info("Checking biometrics for %s", approvalType.String()) + authority, err := polkit.NewAuthority() if err != nil { return false diff --git a/agent/systemauth/systemauth.go b/agent/systemauth/systemauth.go new file mode 100644 index 0000000..8432170 --- /dev/null +++ b/agent/systemauth/systemauth.go @@ -0,0 +1,11 @@ +package systemauth + +import "os" + +var systemAuthDisabled = false + +func init() { + if os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED") == "true" { + systemAuthDisabled = true + } +} diff --git a/agent/unixsocketagent.go b/agent/unixsocketagent.go index 5f59c82..b97356c 100644 --- a/agent/unixsocketagent.go +++ b/agent/unixsocketagent.go @@ -8,7 +8,6 @@ import ( "os" "time" - "github.com/LlamaNite/llamalog" "github.com/quexten/goldwarden/agent/actions" "github.com/quexten/goldwarden/agent/bitwarden" "github.com/quexten/goldwarden/agent/bitwarden/crypto" @@ -17,6 +16,7 @@ import ( "github.com/quexten/goldwarden/agent/ssh" "github.com/quexten/goldwarden/agent/vault" "github.com/quexten/goldwarden/ipc" + "github.com/quexten/goldwarden/logging" "golang.org/x/sys/unix" ) @@ -25,7 +25,7 @@ const ( TokenRefreshInterval = 30 * time.Minute ) -var log = llamalog.NewLogger("Goldwarden", "Agent") +var log = logging.GetLogger("Goldwarden", "Agent") func writeError(c net.Conn, errMsg error) error { payload := ipc.ActionResponse{ @@ -102,17 +102,28 @@ type AgentState struct { config *config.ConfigFile } -func StartUnixAgent(path string, websocket bool, sshAgent bool) error { +func StartUnixAgent(path string, runtimeConfig config.RuntimeConfig) error { ctx := context.Background() // check if exists keyring := crypto.NewKeyring(nil) var vault = vault.NewVault(&keyring) - cfg, err := config.ReadConfig() + cfg, err := config.ReadConfig(runtimeConfig) if err != nil { var cfg = config.DefaultConfig() 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() { log.Warn("Config is not locked. SET A PIN!!") token, err := cfg.GetToken() @@ -134,11 +145,11 @@ func StartUnixAgent(path string, websocket bool, sshAgent bool) error { } disableDumpable() - if websocket { + if !runtimeConfig.WebsocketDisabled { go bitwarden.RunWebsocketDaemon(ctx, vault, &cfg) } - if sshAgent { + if !runtimeConfig.DisableSSHAgent { vaultAgent := ssh.NewVaultAgent(vault) vaultAgent.SetUnlockRequestAction(func() bool { err := cfg.TryUnlock(vault) diff --git a/agent/virtualagent.go b/agent/virtualagent.go index b3174f9..1b7b500 100644 --- a/agent/virtualagent.go +++ b/agent/virtualagent.go @@ -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() // check if exists keyring := crypto.NewKeyring(nil) var vault = vault.NewVault(&keyring) - cfg, err := config.ReadConfig() + cfg, err := config.ReadConfig(runtimeConfig) if err != nil { var cfg = config.DefaultConfig() 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!!") token, err := cfg.GetToken() if err == nil { diff --git a/autofill/autofill.go b/autofill/autofill.go index 981e1a8..c0520df 100644 --- a/autofill/autofill.go +++ b/autofill/autofill.go @@ -11,7 +11,7 @@ import ( "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{ 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{}) if err != nil { return []ipc.DecryptedLoginCipher{}, err @@ -49,8 +49,8 @@ func ListLogins() ([]ipc.DecryptedLoginCipher, error) { } } -func Run(layout string, useCopyPaste bool) { - logins, err := ListLogins() +func Run(layout string, useCopyPaste bool, client client.Client) { + logins, err := ListLogins(client) if err != nil { panic(err) } @@ -65,7 +65,7 @@ func Run(layout string, useCopyPaste bool) { } RunAutofill(autofillEntries, func(uuid string, c chan bool) { - login, err := GetLoginByUUID(uuid) + login, err := GetLoginByUUID(uuid, client) if err != nil { panic(err) } diff --git a/cmd/autofill.go b/cmd/autofill.go index c92bcab..6ce4baf 100644 --- a/cmd/autofill.go +++ b/cmd/autofill.go @@ -1,4 +1,4 @@ -//go:build autofill +//go:build !noautofill package cmd @@ -14,7 +14,7 @@ var autofillCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { layout := cmd.Flag("layout").Value.String() useCopyPaste, _ := cmd.Flags().GetBool("use-copy-paste") - autofill.Run(layout, useCopyPaste) + autofill.Run(layout, useCopyPaste, commandClient) }, } diff --git a/cmd/daemonize.go b/cmd/daemonize.go index 3335ef8..3929d96 100644 --- a/cmd/daemonize.go +++ b/cmd/daemonize.go @@ -15,12 +15,13 @@ var daemonizeCmd = &cobra.Command{ 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: func(cmd *cobra.Command, args []string) { - websocketDisabled := os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true" + websocketDisabled := runtimeConfig.WebsocketDisabled + sshDisabled := runtimeConfig.DisableSSHAgent + if websocketDisabled { println("Websocket disabled") } - sshDisabled := os.Getenv("GOLDWARDEN_SSH_DISABLED") == "true" if sshDisabled { println("SSH agent disabled") } @@ -35,7 +36,7 @@ var daemonizeCmd = &cobra.Command{ if err != nil { panic(err) } - err = agent.StartUnixAgent(home+"/.goldwarden.sock", websocketDisabled, sshDisabled) + err = agent.StartUnixAgent(home+"/.goldwarden.sock", runtimeConfig) if err != nil { panic(err) } diff --git a/cmd/logins.go b/cmd/logins.go new file mode 100644 index 0000000..25788a3 --- /dev/null +++ b/cmd/logins.go @@ -0,0 +1,62 @@ +/* +Copyright © 2023 NAME HERE +*/ +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", "", "") +} diff --git a/cmd/root.go b/cmd/root.go index f522bcd..7c43d13 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,11 +4,14 @@ import ( "os" "github.com/quexten/goldwarden/agent" + "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/client" + "github.com/quexten/goldwarden/ipc" "github.com/spf13/cobra" ) var commandClient client.Client +var runtimeConfig config.RuntimeConfig var rootCmd = &cobra.Command{ Use: "goldwarden", @@ -18,7 +21,17 @@ var rootCmd = &cobra.Command{ 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() if err != nil { os.Exit(1) @@ -26,13 +39,23 @@ func Execute() { } 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") } + +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 +} diff --git a/cmd/ssh.go b/cmd/ssh.go index 6265c1c..25e043e 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -27,6 +27,8 @@ var sshAddCmd = &cobra.Command{ Long: `Runs a command with environment variables from your vault. The variables are stored as a secure note. Consult the documentation for more information.`, Run: func(cmd *cobra.Command, args []string) { + loginIfRequired() + name, _ := cmd.Flags().GetString("name") copyToClipboard, _ := cmd.Flags().GetBool("clipboard") @@ -60,6 +62,8 @@ var listSSHCmd = &cobra.Command{ Short: "Lists all SSH keys in your vault", Long: `Lists all SSH keys in your vault.`, Run: func(cmd *cobra.Command, args []string) { + loginIfRequired() + result, err := commandClient.SendToAgent(ipc.GetSSHKeysRequest{}) if err != nil { println("Error: " + err.Error()) diff --git a/logging/llamalogger.go b/logging/llamalogger.go new file mode 100644 index 0000000..f47517b --- /dev/null +++ b/logging/llamalogger.go @@ -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...) + } +} diff --git a/main.go b/main.go index fd1b5f9..8ca07a1 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/browserbiometrics" "github.com/quexten/goldwarden/cmd" ) @@ -16,10 +17,48 @@ func main() { 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.") time.Sleep(3 * time.Second) } - cmd.Execute() + cmd.Execute(runtimeConfig) }