Skip to content

Commit

Permalink
Add .gitconfig parser
Browse files Browse the repository at this point in the history
This commit adds yet another config handler for gopass. It is based on
the format used by git itself. This has the potential to address a lot
of long standing issues, but it also causes a lot of changes to how we
handle configuration, so bugs are inevitable.

Fixes #1567
Fixes #1764
Fixes #1819
Fixes #1878
Fixes #2387

RELEASE_NOTES=[BREAKING] New config format based on git config.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz committed Nov 9, 2022
1 parent f5067e6 commit 45aaea0
Show file tree
Hide file tree
Showing 106 changed files with 2,635 additions and 1,255 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ test-win: $(GOPASS_OUTPUT)
$(GO) test -test.short -run '(Test|Example)' $(pkg) || exit 1;)

test-integration: $(GOPASS_OUTPUT)
cd tests && GOPASS_BINARY=$(PWD)/$(GOPASS_OUTPUT) GOPASS_TEST_DIR=$(PWD)/tests $(GO) test -v
cd tests && GOPASS_BINARY=$(PWD)/$(GOPASS_OUTPUT) GOPASS_TEST_DIR=$(PWD)/tests $(GO) test -v $(TESTFLAGS)

crosscompile:
@echo -n ">> CROSSCOMPILE linux/amd64"
Expand Down
49 changes: 31 additions & 18 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Some configuration options are only available through setting environment variab
| `GOPASS_GPG_BINARY` | `string` | Set this to the absolute path to the GPG binary if you need to override the value returned by `gpgconf`, e.g. [QubesOS](https://www.qubes-os.org/doc/split-gpg/). |
| `GOPASS_PW_DEFAULT_LENGTH` | `int` | Set to any integer value larger than zero to define a different default length in the `generate` command. By default the length is 24 characters. |
| `GOPASS_AUTOSYNC_INTERVAL` | `int` | Set this to the number of days between autosync runs. |
| `GOPASS_NO_AUTOSYNC` | `bool` | Set this to `true` to disable autosync. |
| `GOPASS_NO_AUTOSYNC` | `bool` | Set this to `true` to disable autosync. Deprecated. Please use `core.autosync` |

Variables not exclusively used by gopass

Expand All @@ -42,31 +42,44 @@ Variables not exclusively used by gopass

## Configuration Options

During start up, gopass will look for a configuration file at `$HOME/.config/gopass/config.yml`. If one is not present, it will create one. If the config file already exists, it will attempt to parse it and load the settings. If this fails, the program will abort. Thus, if gopass is giving you trouble with a broken or incompatible configuration file, simply rename it or delete it.
During start up, gopass will look for a configuration file at `$HOME/.config/gopass/config`. If one is not present, it will create one. If the config file already exists, it will attempt to parse it and load the settings. If this fails, the program will abort. Thus, if gopass is giving you trouble with a broken or incompatible configuration file, simply rename it or delete it.

All configuration options are also available for reading and writing through the sub-command `gopass config`.

* To display all values: `gopass config`
* To display a single value: `gopass config autoclip`
* To update a single value: `gopass config autoclip false`
* As many other sub-commands this command accepts a `--store` flag to operate on a given sub-store, provided the sub-store is a remote one. Support for different local configurations per mount was dropped in v1.9.3.
* As many other sub-commands this command accepts a `--store` flag to operate on a given sub-store, provided the sub-store is a remote one.

### Configuration format

`gopass` uses a configuration format inspired by and mostly compatible with the configuration format used by git. We support
a different configuration sources that take precedence over each other, just like [git](https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-config.html).

#### Configuration precendence

* Hard-coded presets apply if nothing else if set
* System-wide configuration file allows operators or package maintainers to supply system-wide defaults in /etc/gopass/config
* User-wide (aka. global) configuration allows to set per-user settings. This is the closest equivalent to the old gopass configs. Located in `$HOME/.config/gopass/config`
* Per-store (aka. local) configuration allow to set per-store settings, e.g. read-only. Located in `<STORE_DIR>/config`.
* Per-store unversioned (aka `config.worktree`) configuration allows to override versioned per-store settings, e.g. disabling read-only. Located in `<STORE_DIR>/config.worktree`
* Environment variables (or command line flags) override all other values. Read from `GOPASS_CONFIG_KEY_n` and `GOPASS_CONFIG_VALUE_n` up to `GOPASS_CONFIG_COUNT`.

### Configuration options

This is a list of available options:

| **Option** | **Type** | Description |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `askformore` | `bool` | If enabled - it will ask to add more data after use of `generate` command. DEPRECATED in v1.10.0 |
| `autoclip` | `bool` | Always copy the password created by `gopass generate`. Only applies to generate. |
| `autoimport` | `bool` | Import missing keys stored in the pass repository without asking. |
| `autosync` | `bool` | Always do a `git push` after a commit to the store. Makes sure your local changes are always available on your git remote. DEPRECATED in v1.10.0 |
| `concurrency` | `int` | Number of threads to use for batch operations (such as reencrypting). DEPRECATED in v1.9.3 |
| `cliptimeout` | `int` | How many seconds the secret is stored when using `-c`. |
| `exportkeys` | `bool` | Export public keys of all recipients to the store. |
| `recipient_hash` | `map` | Map of recipient ids to their hashes. DEPRECATED in v1.10.0 |
| `usesymbols` | `bool` | If enabled - it will use symbols when generating passwords. DEPRECATED in v1.9.3 |
| `nocolor` | `bool` | Do not use color. |
| `nopager` | `bool` | Do not invoke a pager to display long lists. |
| `notifications` | `bool` | Enable desktop notifications. |
| `parsing` | `bool` | Enable parsing of output to have key-value and yaml secrets. |
| `path` | `string` | Path to the root store. |
| `safecontent` | `bool` | Only output _safe content_ (i.e. everything but the first line of a secret) to the terminal. Use _copy_ (`-c`) to retrieve the password in the clipboard, or _force_ (`-f`) to still print it. |
| `core.autoclip` | `bool` | Always copy the password created by `gopass generate`. Only applies to generate. |
| `core.autoimport` | `bool` | Import missing keys stored in the pass repository without asking. |
| `core.autosync` | `bool` | Always do a `git push` after a commit to the store. Makes sure your local changes are always available on your git remote. |
| `core.cliptimeout` | `int` | How many seconds the secret is stored when using `-c`. |
| `core.exportkeys` | `bool` | Export public keys of all recipients to the store. |
| `core.nocolor` | `bool` | Do not use color. |
| `core.pager` | `bool` | Do not invoke a pager to display long lists. |
| `core.notifications` | `bool` | Enable desktop notifications. |
| `core.parsing` | `bool` | Enable parsing of output to have key-value and yaml secrets. |
| `core.readonly` | `bool` | Disable writing to a store. Note: This is just a convenience option to prevent accidential writes. Enforcement can only happen on a central server (if repos are set up around a central one). |
| `mounts.path` | `string` | Path to the root store. |
| `core.showsafecontent` | `bool` | Only output _safe content_ (i.e. everything but the first line of a secret) to the terminal. Use _copy_ (`-c`) to retrieve the password in the clipboard, or _force_ (`-f`) to still print it. |
36 changes: 0 additions & 36 deletions fish.completion
Original file line number Diff line number Diff line change
Expand Up @@ -51,42 +51,6 @@ complete -c $PROG -f -n '__fish_gopass_uses_command age identities -l chars -d "
complete -c $PROG -f -n '__fish_gopass_uses_command age identities -l help -d "show help"'
complete -c $PROG -f -n '__fish_gopass_uses_command age identities -l version -d "print the version"'
complete -c $PROG -f -n '__fish_gopass_needs_command' -a alias -d 'Command: Manage domain aliases'
complete -c $PROG -f -n '__fish_gopass_uses_command alias' -a add -d 'Subcommand: Add a new alias'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l yes -d "Always answer yes to yes/no questions"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l clip -d "Copy the password value into the clipboard"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l alsoclip -d "Copy the password and show everything"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l qr -d "Print the password as a QR Code"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l unsafe -d "Display unsafe content (e.g. the password) even if safecontent is enabled"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l password -d "Display only the password. Takes precedence over all other flags."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l revision -d "Show a past revision. Does NOT support RCS specific shortcuts. Use exact revision or -&lt;N&gt; to select the Nth oldest revision of this entry."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l noparsing -d "Do not parse the output."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l chars -d "Print specific characters from the secret"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l help -d "show help"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias add -l version -d "print the version"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias' -a remove -d 'Subcommand: Remove an alias from a domain'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l yes -d "Always answer yes to yes/no questions"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l clip -d "Copy the password value into the clipboard"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l alsoclip -d "Copy the password and show everything"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l qr -d "Print the password as a QR Code"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l unsafe -d "Display unsafe content (e.g. the password) even if safecontent is enabled"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l password -d "Display only the password. Takes precedence over all other flags."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l revision -d "Show a past revision. Does NOT support RCS specific shortcuts. Use exact revision or -&lt;N&gt; to select the Nth oldest revision of this entry."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l noparsing -d "Do not parse the output."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l chars -d "Print specific characters from the secret"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l help -d "show help"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias remove -l version -d "print the version"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias' -a delete -d 'Subcommand: Delete an entire domain'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l yes -d "Always answer yes to yes/no questions"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l clip -d "Copy the password value into the clipboard"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l alsoclip -d "Copy the password and show everything"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l qr -d "Print the password as a QR Code"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l unsafe -d "Display unsafe content (e.g. the password) even if safecontent is enabled"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l password -d "Display only the password. Takes precedence over all other flags."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l revision -d "Show a past revision. Does NOT support RCS specific shortcuts. Use exact revision or -&lt;N&gt; to select the Nth oldest revision of this entry."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l noparsing -d "Do not parse the output."'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l chars -d "Print specific characters from the secret"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l help -d "show help"'
complete -c $PROG -f -n '__fish_gopass_uses_command alias delete -l version -d "print the version"'
complete -c $PROG -f -n '__fish_gopass_needs_command' -a audit -d 'Command: Decrypt all secrets and scan for weak or leaked passwords'
complete -c $PROG -f -n '__fish_gopass_needs_command' -a cat -d 'Command: Decode and print content of a binary secret to stdout, or encode and insert from stdin'
complete -c $PROG -f -n '__fish_gopass_needs_command' -a clone -d 'Command: Clone a password store from a git repository'
Expand Down
16 changes: 10 additions & 6 deletions internal/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ import (
)

func newMock(ctx context.Context, path string) (*Action, error) {
cfg := config.Load()
cfg.Path = path
cfg := config.NewNoWrites()
if err := cfg.SetPath(path); err != nil {
return nil, err
}
ctx = cfg.WithConfig(ctx)

if !backend.HasCryptoBackend(ctx) {
ctx = backend.WithCryptoBackend(ctx, backend.Plain)
Expand Down Expand Up @@ -54,6 +57,7 @@ func TestAction(t *testing.T) {
act, err := newMock(ctx, u.StoreDir(""))
require.NoError(t, err)
require.NotNil(t, act)
ctx = act.cfg.WithConfig(ctx) //nolint:ineffassign

actName := "action.test"

Expand All @@ -71,7 +75,7 @@ func TestNew(t *testing.T) {
t.Parallel()

td := t.TempDir()
cfg := config.New()
cfg := config.NewNoWrites()
sv := semver.Version{}

t.Run("init a new store", func(t *testing.T) { //nolint:paralleltest
Expand All @@ -80,9 +84,9 @@ func TestNew(t *testing.T) {
})

t.Run("init an existing plain store", func(t *testing.T) { //nolint:paralleltest
cfg.Path = filepath.Join(td, "store")
assert.NoError(t, os.MkdirAll(cfg.Path, 0o700))
assert.NoError(t, os.WriteFile(filepath.Join(cfg.Path, plain.IDFile), []byte("foobar"), 0o600))
require.NoError(t, cfg.SetPath(filepath.Join(td, "store")))
assert.NoError(t, os.MkdirAll(cfg.Path(), 0o700))
assert.NoError(t, os.WriteFile(filepath.Join(cfg.Path(), plain.IDFile), []byte("foobar"), 0o600))
_, err := New(cfg, sv)
assert.NoError(t, err)
})
Expand Down
60 changes: 1 addition & 59 deletions internal/action/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import (
"sort"
"strings"

"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/pwgen/pwrules"
"github.com/urfave/cli/v2"
)

// AliasesPrint prints all cofigured aliases.
func (s *Action) AliasesPrint(c *cli.Context) error {
out.Printf(c.Context, "Configured aliases:")
aliases := pwrules.AllAliases()
aliases := pwrules.AllAliases(c.Context)
keys := make([]string, 0, len(aliases))
for k := range aliases {
keys = append(keys, k)
Expand All @@ -27,59 +25,3 @@ func (s *Action) AliasesPrint(c *cli.Context) error {

return nil
}

// AliasesAdd adds a single alias to a domain.
func (s *Action) AliasesAdd(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
domain := c.Args().First()
alias := c.Args().Get(1)

if domain == "" || alias == "" {
return exit.Error(exit.Usage, nil, "Usage: %s alias add <domain> <alias>", s.Name)
}

if err := pwrules.AddCustomAlias(domain, alias); err != nil {
return err
}

out.Printf(ctx, "Added alias %q to domain %q", alias, domain)

return nil
}

// AliasesRemove removes a single alias from a domain.
func (s *Action) AliasesRemove(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
domain := c.Args().First()
alias := c.Args().Get(1)

if domain == "" || alias == "" {
return exit.Error(exit.Usage, nil, "Usage: %s alias remove <domain> <alias>", s.Name)
}

if err := pwrules.RemoveCustomAlias(domain, alias); err != nil {
return err
}

out.Printf(ctx, "Remove alias %q from domain %q", alias, domain)

return nil
}

// AliasesDelete remove an alias mapping for a domain.
func (s *Action) AliasesDelete(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
domain := c.Args().First()

if domain == "" {
return exit.Error(exit.Usage, nil, "Usage: %s alias delete <domain>", s.Name)
}

if err := pwrules.DeleteCustomAlias(domain); err != nil {
return err
}

out.Printf(ctx, "Remove aliases for domain %q", domain)

return nil
}
1 change: 1 addition & 0 deletions internal/action/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestAudit(t *testing.T) { //nolint:paralleltest
act, err := newMock(ctx, u.StoreDir(""))
require.NoError(t, err)
require.NotNil(t, act)
ctx = act.cfg.WithConfig(ctx)

buf := &bytes.Buffer{}
out.Stdout = buf
Expand Down

0 comments on commit 45aaea0

Please sign in to comment.