Skip to content

Commit

Permalink
feat: Add support for Keeper password manager
Browse files Browse the repository at this point in the history
Co-authored-by: Tag Howard <tag.howard1080@gmail.com>
  • Loading branch information
twpayne and jthoward64 committed May 19, 2022
1 parent 7485aea commit e0b3ace
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 4 deletions.
Expand Up @@ -59,6 +59,8 @@ The following configuration variables are available:
| `keepassxc` | `args` | []string | *none* | Extra args to KeePassXC CLI command |
| | `command` | string | `keepassxc-cli` | KeePassXC CLI command |
| | `database` | string | *none* | KeePassXC database |
| `keeper` | `args` | []string | *none* | Extra args to Keeper CLI command |
| | `command` | string | `keeper` | Keeper CLI command |
| `lastpass` | `command` | string | `lpass` | Lastpass CLI command |
| `merge` | `args` | []string | *see `merge` below* | Args to 3-way merge command |
| | `command` | string | `vimdiff` | 3-way merge command |
Expand Down
@@ -0,0 +1,7 @@
# Keeper functions

The `keeper*` functions return data from [Keeper](https://www.keepersecurity.com/)
[Commander CLI](https://docs.keeper.io/secrets-manager/commander-cli) (`keeper`).

The command used can by changed by setting the `keeper.command` configuration
variable, and extra arguments can be added by setting `keeper.args`.
@@ -0,0 +1,6 @@
# `keeper` *uid*

`keeper` returns structured data retreived from
[Keeper](https://www.keepersecurity.com/) using the [Commander
CLI](https://docs.keeper.io/secrets-manager/commander-cli). *uid* is passed to
`keeper get --format=json` and the output is parsed as JSON.
@@ -0,0 +1,12 @@
# `keeperDataFields` *uid*

`keeperDataFields` returns the `.data.fields` elements of `keeper get
--format=json *uid*` indexed by `type`.

## Example

```
url = {{ (keeperDataFields "$UID").url }}
login = {{ index (keeperDataFields "$UID").login 0 }}
password = {{ index (keeperDataFields "$UID").password 0 }}
```
@@ -0,0 +1,4 @@
# `keeperFindPassword` *query*

`keeperFindPassword` returns the output of `keeper find-password query`. *query*
can be a UID or a path.
Expand Up @@ -14,4 +14,5 @@ way:
| HashiCorp Vault | `vault` | `{{ secretJSON "kv" "get" "-format=json" "$ID" }}` |
| LastPass | `lpass` | `{{ secretJSON "show" "--json" "$ID" }}` |
| KeePassXC | `keepassxc-cli` | Not possible (interactive command only) |
| Keeper | `keeper` | `{{ secretJSON "get" "--format=json" "$ID" }}` |
| pass | `pass` | `{{ secret "show" "$ID" }}` |
35 changes: 35 additions & 0 deletions assets/chezmoi.io/docs/user-guide/password-managers/keeper.md
@@ -0,0 +1,35 @@
# Keeper

chezmoi includes support for [Keeper](https://www.keepersecurity.com/) using the
[Commander CLI](https://docs.keeper.io/secrets-manager/commander-cli) to expose
data as a template function.

Create a persistent login session as [described in the Command CLI
documentation](https://docs.keeper.io/secrets-manager/commander-cli/using-commander/logging-in#persistent-login-sessions).

Passwords can be retrieved with the `keeperFindPassword` template function, for
example:

```
examplePasswordFromPath = {{ keeperFindPassword "$PATH" }}
examplePasswordFromUid = {{ keeperFindPassword "$UID" }}
```

For retrieving more complex data, use the `keeper` template function with a UID
to retrieve structured data from [`keeper
get`](https://docs.keeper.io/secrets-manager/commander-cli/using-commander/command-reference/record-commands#get-command)
or the `keeperDataFields` template function which restructures the output of
`keeper get` in to a more convenient form, for example:

```
keeperDataTitle = {{ (keeper "$UID").data.title }}
examplePassword = {{ index (keeperDataFields "$UID").password 0 }}
```

Extra arguments can be passed to the Keeper CLI command by setting the
`keeper.args` variable in chezmoi's config file, for example:

```toml title="~/.config/chezmoi/chezmoi.toml"
[keeper]
args = ["--config", "/path/to/config.json"]
```
8 changes: 4 additions & 4 deletions assets/chezmoi.io/docs/what-does-chezmoi-do.md
Expand Up @@ -44,10 +44,10 @@ in a git repo under your control. You can write the configuration file in the
format of your choice. chezmoi can retrieve secrets from
[1Password](https://1password.com/), [Bitwarden](https://bitwarden.com/),
[gopass](https://www.gopass.pw/), [KeePassXC](https://keepassxc.org/),
[LastPass](https://lastpass.com/), [pass](https://www.passwordstore.org/),
[Vault](https://www.vaultproject.io/), Keychain,
[Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), or any command-line
utility of your choice. You can encrypt individual files with
[Keeper](https://www.keepersecurity.com/), [LastPass](https://lastpass.com/),
[pass](https://www.passwordstore.org/), [Vault](https://www.vaultproject.io/),
Keychain, [Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), or any
command-line utility of your choice. You can encrypt individual files with
[GnuPG](https://www.gnupg.org) or [age](https://age-encryption.org). You can
checkout your dotfiles repo on as many machines as you want without revealing
any secrets to anyone.
Expand Down
6 changes: 6 additions & 0 deletions assets/chezmoi.io/mkdocs.yml
Expand Up @@ -49,6 +49,7 @@ nav:
- gopass: user-guide/password-managers/gopass.md
- KeePassXC: user-guide/password-managers/keepassxc.md
- Keychain and Windows Credentials Manager: user-guide/password-managers/keychain-and-windows-credentials-manager.md
- Keeper: user-guide/password-managers/keeper.md
- LastPass: user-guide/password-managers/lastpass.md
- pass: user-guide/password-managers/pass.md
- Vault: user-guide/password-managers/vault.md
Expand Down Expand Up @@ -192,6 +193,11 @@ nav:
- keepassxc: reference/templates/keepassxc-functions/keepassxc.md
- keepassxcAttachment: reference/templates/keepassxc-functions/keepassxcAttachment.md
- keepassxcAttribute: reference/templates/keepassxc-functions/keepassxcAttribute.md
- Keeper functions:
- reference/templates/keeper-functions/index.md
- keeper: reference/templates/keeper-functions/keeper.md
- keeperDataFields: reference/templates/keeper-functions/keeperDataFields.md
- keeperFindPassword: reference/templates/keeper-functions/keeperFindPassword.md
- Keyring functions:
- keyring: reference/templates/keyring-functions/keyring.md
- LastPass functions:
Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/config.go
Expand Up @@ -90,6 +90,7 @@ type ConfigFile struct {
Bitwarden bitwardenConfig `mapstructure:"bitwarden"`
Gopass gopassConfig `mapstructure:"gopass"`
Keepassxc keepassxcConfig `mapstructure:"keepassxc"`
Keeper keeperConfig `mapstructure:"keeper"`
Lastpass lastpassConfig `mapstructure:"lastpass"`
Onepassword onepasswordConfig `mapstructure:"onepassword"`
Pass passConfig `mapstructure:"pass"`
Expand Down Expand Up @@ -275,6 +276,9 @@ func newConfig(options ...configOption) (*Config, error) {
Keepassxc: keepassxcConfig{
Command: "keepassxc-cli",
},
Keeper: keeperConfig{
Command: "keeper",
},
Lastpass: lastpassConfig{
Command: "lpass",
},
Expand Down Expand Up @@ -438,6 +442,9 @@ func newConfig(options ...configOption) (*Config, error) {
"keepassxc": c.keepassxcTemplateFunc,
"keepassxcAttachment": c.keepassxcAttachmentTemplateFunc,
"keepassxcAttribute": c.keepassxcAttributeTemplateFunc,
"keeper": c.keeperTemplateFunction,
"keeperDataFields": c.keeperDataFieldsTemplateFunction,
"keeperFindPassword": c.keeperFindPasswordTemplateFunc,
"keyring": c.keyringTemplateFunc,
"lastpass": c.lastpassTemplateFunc,
"lastpassRaw": c.lastpassRawTemplateFunc,
Expand Down
8 changes: 8 additions & 0 deletions pkg/cmd/doctorcmd.go
Expand Up @@ -308,6 +308,14 @@ func (c *Config) runDoctorCmd(cmd *cobra.Command, args []string) error {
versionArgs: []string{"--version"},
versionRx: regexp.MustCompile(`^(\d+\.\d+\.\d+)`),
},
&binaryCheck{
name: "keeper-command",
binaryname: c.Keeper.Command,
ifNotSet: checkResultWarning,
ifNotExist: checkResultInfo,
versionArgs: []string{"version"},
versionRx: regexp.MustCompile(`^Commander\s+Version:\s+(\d+\.\d+\.\d+)`),
},
&fileCheck{
name: "keepassxc-db",
filename: c.Keepassxc.Database,
Expand Down
81 changes: 81 additions & 0 deletions pkg/cmd/keepertemplatefuncs.go
@@ -0,0 +1,81 @@
package cmd

import (
"bytes"
"encoding/json"
"os"
"os/exec"
"strings"
)

type keeperConfig struct {
Command string
Args []string
outputCache map[string][]byte
}

func (c *Config) keeperTemplateFunction(record string) map[string]interface{} {
output, err := c.keeperOutput([]string{"get", "--format=json", record})
if err != nil {
panic(err)
}
var result map[string]interface{}
if err := json.Unmarshal(output, &result); err != nil {
panic(err)
}
return result
}

func (c *Config) keeperDataFieldsTemplateFunction(record string) map[string]interface{} {
output, err := c.keeperOutput([]string{"get", "--format=json", record})
if err != nil {
panic(err)
}
var data struct {
Data struct {
Fields []struct {
Type string `json:"type"`
Value interface{} `json:"value"`
} `json:"fields"`
} `json:"data"`
}
if err := json.Unmarshal(output, &data); err != nil {
panic(err)
}
result := make(map[string]interface{})
for _, field := range data.Data.Fields {
result[field.Type] = field.Value
}
return result
}

func (c *Config) keeperFindPasswordTemplateFunc(record string) string {
output, err := c.keeperOutput([]string{"find-password", record})
if err != nil {
panic(err)
}
return string(bytes.TrimSpace(output))
}

func (c *Config) keeperOutput(args []string) ([]byte, error) {
key := strings.Join(args, "\x00")
if data, ok := c.Keeper.outputCache[key]; ok {
return data, nil
}

name := c.Keeper.Command
args = append(args, c.Keeper.Args...)
cmd := exec.Command(name, args...)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
output, err := c.baseSystem.IdempotentCmdOutput(cmd)
if err != nil {
return nil, newCmdOutputError(cmd, output, err)
}

if c.Keeper.outputCache == nil {
c.Keeper.outputCache = make(map[string][]byte)
}
c.Keeper.outputCache[key] = output
return output, nil
}
6 changes: 6 additions & 0 deletions pkg/cmd/testdata/scripts/doctor_unix.txt
Expand Up @@ -6,6 +6,7 @@ chmod 755 bin/git
chmod 755 bin/gopass
chmod 755 bin/gpg
chmod 755 bin/keepassxc
chmod 755 bin/keeper
chmod 755 bin/lpass
chmod 755 bin/op
chmod 755 bin/pass
Expand Down Expand Up @@ -45,6 +46,7 @@ stdout '^ok\s+bitwarden-command\s+'
stdout '^ok\s+gopass-command\s+'
stdout '^ok\s+keepassxc-command\s+'
stdout '^info\s+keepassxc-db\s+'
stdout '^ok\s+keeper-command\s+'
stdout '^ok\s+lastpass-command\s+'
stdout '^ok\s+pass-command\s+'
stdout '^ok\s+vault-command\s+'
Expand Down Expand Up @@ -101,6 +103,10 @@ echo "Compression: Uncompressed, ZIP, ZLIB, BZIP2"
#!/bin/sh

echo "2.5.4"
-- bin/keeper --
#!/bin/sh

echo "Commander Version: 16.6.4 (Current version)"
-- bin/lpass --
#!/bin/sh

Expand Down
117 changes: 117 additions & 0 deletions pkg/cmd/testdata/scripts/keeper.txt
@@ -0,0 +1,117 @@
[!windows] chmod 755 bin/keeper
[windows] unix2dos bin/keeper.cmd

# test keeper template function
chezmoi execute-template '{{ (keeper "QOahgRH_dSTvSvhRBqzCzQ").record_uid }}'
stdout '^QOahgRH_dSTvSvhRBqzCzQ$'

# test keeperDataFields template function
chezmoi execute-template '{{ index (keeperDataFields "QOahgRH_dSTvSvhRBqzCzQ").password 0 }}'
stdout ^mypassword$

# test keeperFindPassword template function
chezmoi execute-template '{{ keeperFindPassword "Example" }}'
stdout ^mypassword$

-- bin/keeper --
#!/bin/sh

case "$*" in
"get --format=json QOahgRH_dSTvSvhRBqzCzQ --config /path/to/config.json")
cat <<EOF
{
"record_uid": "QOahgRH_dSTvSvhRBqzCzQ",
"data": {
"title": "Example",
"type": "login",
"fields": [
{
"type": "login",
"value": [
"mylogin"
]
},
{
"type": "password",
"value": [
"mypassword"
]
},
{
"type": "url",
"value": []
},
{
"type": "fileRef",
"value": []
},
{
"type": "oneTimeCode",
"value": []
}
],
"custom": []
}
}
EOF
;;
"find-password Example --config /path/to/config.json")
echo "mypassword"
;;
*)
cat <<EOF
Commands:
search ... Search the vault. Can use a regular expression.

Type 'command -h' to display help on command
EOF
esac
-- bin/keeper.cmd --
@echo off
IF "%*" == "get --format=json QOahgRH_dSTvSvhRBqzCzQ --config /path/to/config.json" (
echo.{
echo. "record_uid": "QOahgRH_dSTvSvhRBqzCzQ",
echo. "data": {
echo. "title": "Example",
echo. "type": "login",
echo. "fields": [
echo. {
echo. "type": "login",
echo. "value": [
echo. "mylogin"
echo. ]
echo. },
echo. {
echo. "type": "password",
echo. "value": [
echo. "mypassword"
echo. ]
echo. },
echo. {
echo. "type": "url",
echo. "value": []
echo. },
echo. {
echo. "type": "fileRef",
echo. "value": []
echo. },
echo. {
echo. "type": "oneTimeCode",
echo. "value": []
echo. }
echo. ],
echo. "custom": []
echo. }
echo.}
) ELSE IF "%*" == "find-password Example --config /path/to/config.json" (
echo.mypassword
;;
) ELSE (
echo.Commands:
echo. search ... Search the vault. Can use a regular expression.
echo.
echo.Type 'command -h' to display help on command
)
-- home/user/.config/chezmoi/chezmoi.toml --
[keeper]
args = ["--config", "/path/to/config.json"]

0 comments on commit e0b3ace

Please sign in to comment.