Skip to content

Commit

Permalink
[FEATURE] Allow for non-interactive age setup (#2970)
Browse files Browse the repository at this point in the history
* [FEATURE] Allow for non-interactive age setup

Also updates Go to Go 1.23.2 and get rid of min and max functions

Signed-off-by: Yolan Romailler <[email protected]>

* [n/a] also renaming clear for Windows

Signed-off-by: Yolan Romailler <[email protected]>

* [n/a] bumping our GHA to Go 1.23

Signed-off-by: Yolan Romailler <[email protected]>

* [n/a] make our harden runner softer

Signed-off-by: Yolan Romailler <[email protected]>

* [n/a] make our harden runner accept go.dev

Signed-off-by: Yolan Romailler <[email protected]>

* [n/a] applying code review changes

Signed-off-by: Yolan Romailler <[email protected]>

---------

Signed-off-by: Yolan Romailler <[email protected]>
  • Loading branch information
AnomalRoil authored Oct 14, 2024
1 parent 75baa9b commit 4c2caf3
Show file tree
Hide file tree
Showing 31 changed files with 117 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/autorelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/go/pkg/mod
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
raw.githubusercontent.com:443
storage.googleapis.com:443
sum.golang.org:443
golang.org:443
go.dev:443
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with:
Expand All @@ -38,7 +40,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/go/pkg/mod
Expand Down Expand Up @@ -92,7 +94,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'

- run: git config --global user.name nobody
- run: git config --global user.email [email protected]
Expand All @@ -115,7 +117,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'

- run: git config --global user.name nobody
- run: git config --global user.email [email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: golangci-lint
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/grype.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: '1.22'
go-version: '1.23'
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/go/pkg/mod
Expand Down
10 changes: 10 additions & 0 deletions docs/backends/age.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ This will automatically create a new age keypair and initialize the new store.

Existing stores can be migrated using `gopass convert --crypto age`.

N.B. for a fully scripted or **non-interactive setup**, you can use the `GOPASS_AGE_PASSWORD` env variable
to set your identity file secret passphrase, and specify the age identity and recipients
that should be used for encrypting/decrypting passwords as follows:
```
$ gopass age identity add <AGE-...> <age1...>
$ GOPASS_AGE_PASSWORD=mypassword gopass init --crypto age <age1...>
```
Notice the extra space in front of the command to skip most shell's history.
You'll need to set your name and username using `git` directly if you're using it as storage backend (the default one).

## Features

* Encryption using `age` library, can be decrypted using the `age` CLI
Expand Down
3 changes: 2 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Some configuration options are only available through setting environment variab
| **Option** | **Type** | **Description** |
|------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CHECKPOINT_DISABLE` | `bool` | Set to any non-empty value to disable calling the GitHub API when running `gopass version`. |
| `GOPASS_AGE_PASSWORD` | `string` | Set to any value (including the empty string) to use as a password for the age identity file containing your secret age identities. |
| `GOPASS_AUTOSYNC_INTERVAL` | `int` | Set this to the number of days between autosync runs. |
| `GOPASS_CHARACTER_SET` | `bool` | Set to any non-empty value to restrict the characters used in generated passwords |
| `GOPASS_CLIPBOARD_CLEAR_CMD` | `string` | Use an external command to remove a password from the clipboard. See [GPaste](usecases/gpaste.md) for an example |
Expand All @@ -20,7 +21,7 @@ Some configuration options are only available through setting environment variab
| `GOPASS_DEBUG_LOG_SECRETS` | `bool` | Set to any non-empty value to enable logging of credentials |
| `GOPASS_DEBUG_LOG` | `string` | Set to a filename to enable debug logging (only set GOPASS_DEBUG to log to stderr) |
| `GOPASS_DEBUG` | `bool` | Set to any non-empty value to enable verbose debug output, by default on stderr, unless GOPASS_DEBUG_LOG is set |
| `GOPASS_DEBUG_VERBOSE` | `int` | Set to any integer value larger than zero to increase the verbosity of debug output |
| `GOPASS_DEBUG_VERBOSE` | `int` | Set to any integer value larger than zero to increase the verbosity of debug output |
| `GOPASS_EXTERNAL_PWGEN` | `string` | Use an external password generator. See [Features](features.md#using-custom-password-generators) for details |
| `GOPASS_FORCE_CHECK` | `string` | (internal) Force the updater to check for updates. Used for testing. |
| `GOPASS_FORCE_UPDATE` | `bool` | Set to any non-empty value to force an update (if available) |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/gopasspw/gopass

go 1.22.1
go 1.23.2

require (
filippo.io/age v1.2.1-0.20240618131852-7eedd929a6cf
Expand Down
3 changes: 2 additions & 1 deletion internal/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ func newMock(ctx context.Context, path string) (*Action, error) {
c := cli.NewContext(cli.NewApp(), fs, nil)
c.Context = ctx
if err := act.IsInitialized(c); err != nil {
return nil, err
// we still return the action since this might be expected sometimes
return act, err
}

return act, nil
Expand Down
5 changes: 2 additions & 3 deletions internal/action/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package action
import (
"context"
"fmt"
"github.com/gopasspw/gopass/internal/tree"
"path/filepath"
"strings"

"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/tree"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/termio"

"github.com/urfave/cli/v2"
)

Expand Down Expand Up @@ -45,7 +46,6 @@ func (s *Action) copy(ctx context.Context, from, to string, force bool) error {

func (s *Action) copyFlattenDir(ctx context.Context, from, to string, force bool) error {
entries, err := s.Store.List(ctx, tree.INF)

if err != nil {
return exit.Error(exit.List, err, "failed to list entries in %q", from)
}
Expand All @@ -69,7 +69,6 @@ func (s *Action) copyFlattenDir(ctx context.Context, from, to string, force bool
}

func (s *Action) copyRegular(ctx context.Context, from, to string, force bool) error {

if !force {
if s.Store.Exists(ctx, to) && !termio.AskForConfirmation(ctx, fmt.Sprintf("%s already exists. Overwrite it?", to)) {
return exit.Error(exit.Aborted, nil, "not overwriting your current secret")
Expand Down
1 change: 0 additions & 1 deletion internal/action/copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,4 @@ func TestCopyWithTrailingSlash(t *testing.T) {
require.NoError(t, act.show(ctx, c, "new/baz", false))
assert.Equal(t, "another\n", buf.String())
buf.Reset()

}
10 changes: 5 additions & 5 deletions internal/action/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,13 @@ func getPwLengthFromEnvOrAskUser(ctx context.Context) (int, error) {
return pwlen, nil
}

func clamp(min, max, value int) int {
if value < min {
return min
func clamp(mi, ma, value int) int {
if value < mi {
return mi
}

if value > max && max > 0 {
return max
if value > ma && ma > 0 {
return ma
}

return value
Expand Down
29 changes: 15 additions & 14 deletions internal/action/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,27 @@ func (s *Action) initGenerateIdentity(ctx context.Context, crypto backend.Crypto

passphrase := xkcdgen.Random()
pwGenerated := true
want, err := termio.AskForBool(ctx, "⚠ Do you want to enter a passphrase? (otherwise we generate one for you)", false)
if err != nil {
return err
}
if want {
pwGenerated = false
sv, err := termio.AskForPassword(ctx, "passphrase for your new keypair", true)
if err != nil {
return fmt.Errorf("failed to read passphrase: %w", err)
}
passphrase = sv
}

// support fully automated setup (e.g. for tests)
if !ctxutil.IsInteractive(ctx) && ctxutil.HasPasswordCallback(ctx) {
//nolint:nestif
if ctxutil.HasPasswordCallback(ctx) {
pw, err := ctxutil.GetPasswordCallback(ctx)("", true)
if err == nil {
passphrase = string(pw)
}
pwGenerated = false
} else {
want, err := termio.AskForBool(ctx, "⚠ Do you want to enter a passphrase? (otherwise we generate one for you)", false)
if err != nil {
return err
}
if want {
pwGenerated = false
sv, err := termio.AskForPassword(ctx, "passphrase for your new keypair", true)
if err != nil {
return fmt.Errorf("failed to read passphrase: %w", err)
}
passphrase = sv
}
}

if crypto.Name() == "gpgcli" {
Expand Down
10 changes: 3 additions & 7 deletions internal/action/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ func TestSetupAgeGitFS(t *testing.T) {
})
ctx = ctxutil.WithPasswordPurgeCallback(ctx, func(s string) {}) //nolint:staticcheck

t.Skip("TODO: fix setup test")

act, err := newMock(ctx, u.StoreDir(""))
require.NoError(t, err)
require.ErrorContains(t, err, "not initialized")
require.NotNil(t, act)

buf := &bytes.Buffer{}
Expand All @@ -56,11 +54,11 @@ func TestSetupAgeGitFS(t *testing.T) {
require.NotNil(t, crypto)
assert.Equal(t, "age", crypto.Name())
assert.True(t, act.initHasUseablePrivateKeys(ctx, crypto))
require.Error(t, act.initGenerateIdentity(ctx, crypto, "foo bar", "[email protected]"))
require.NoError(t, act.initGenerateIdentity(ctx, crypto, "foo bar", "[email protected]"))
buf.Reset()

act.printRecipients(ctx, "")
assert.Contains(t, buf.String(), "0xDEADBEEF")
assert.Contains(t, buf.String(), "age1")
buf.Reset()
}

Expand Down Expand Up @@ -89,8 +87,6 @@ func TestSetupPlainFS(t *testing.T) {
require.NoError(t, act.IsInitialized(c))
buf.Reset()

t.Skip("TODO: fix these tests")

require.Error(t, act.Init(c))
assert.Contains(t, buf.String(), "already initialized")
buf.Reset()
Expand Down
6 changes: 3 additions & 3 deletions internal/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ func (a *Auditor) Batch(ctx context.Context, secrets []string) (*Report, error)
// https://github.com/gopasspw/gopass/pull/245
//
maxJobs := a.s.Concurrency()
if max := config.Int(ctx, "audit.concurrency"); max > 0 {
if maxJobs > max {
maxJobs = max
if maxVal := config.Int(ctx, "audit.concurrency"); maxVal > 0 {
if maxJobs > maxVal {
maxJobs = maxVal
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/backend/crypto/age/identities.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,11 @@ func IdentityToRecipient(id age.Identity) age.Recipient {

// GenerateIdentity creates a new identity.
func (a *Age) GenerateIdentity(ctx context.Context, _ string, _ string, pw string) error {
// we don't check if the password callback is set, since it could only be
// set through an env variable, and here pw can only be set through an
// actual user input.
if pw != "" {
debug.Log("age GenerateIdentity using provided pw")
ctx = ctxutil.WithPasswordCallback(ctx, func(prompt string, confirm bool) ([]byte, error) {
return []byte(pw), nil
})
Expand Down
4 changes: 3 additions & 1 deletion internal/backend/crypto/age/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/gopasspw/gopass/internal/backend"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/fsutil"
)

const (
Expand All @@ -26,7 +27,8 @@ func (l loader) New(ctx context.Context) (backend.Crypto, error) {
}

func (l loader) Handles(ctx context.Context, s backend.Storage) error {
if s.Exists(ctx, OldIDFile) || s.Exists(ctx, OldKeyring) {
// OldKeyring is meant to be in the config folder, not necessarily in the store
if s.Exists(ctx, OldIDFile) || fsutil.IsNonEmptyFile(OldKeyring) {
if err := migrate(ctx, s); err != nil {
out.Errorf(ctx, "Failed to migrate age backend: %s", err)
}
Expand Down
8 changes: 0 additions & 8 deletions internal/store/root/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,3 @@ func (r *Store) Concurrency() int {

return min(c, runtime.NumCPU())
}

func min(a, b int) int {
if a < b {
return a
}

return b
}
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,15 @@ func initContext(ctx context.Context, cfg *config.Config) context.Context {
}
}

// using a password callback for age identity file or not?
if pw, isSet := os.LookupEnv("GOPASS_AGE_PASSWORD"); isSet {
ctx = ctxutil.WithPasswordCallback(ctx, func(_ string, _ bool) ([]byte, error) {
debug.Log("using age password callback from env variable GOPASS_AGE_PASSWORD")

return []byte(pw), nil
})
}

return ctx
}

Expand Down
12 changes: 6 additions & 6 deletions pkg/clipboard/clipboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,22 @@ func CopyTo(ctx context.Context, name string, content []byte, timeout int) error
}

if timeout < 1 {
debug.Log("Auto-clear of clipboard disabled.")
debug.Log("Auto-clearClip of clipboard disabled.")

out.Printf(ctx, "✔ Copied %s to clipboard.", color.YellowString(name))
_ = notify.Notify(ctx, "gopass - clipboard", fmt.Sprintf("✔ Copied %s to clipboard.", name))

return nil
}

if err := clear(ctx, name, content, timeout); err != nil {
_ = notify.Notify(ctx, "gopass - clipboard", "failed to clear clipboard")
if err := clearClip(ctx, name, content, timeout); err != nil {
_ = notify.Notify(ctx, "gopass - clipboard", "failed to clearClip clipboard")

return fmt.Errorf("failed to clear clipboard: %w", err)
return fmt.Errorf("failed to clearClip clipboard: %w", err)
}

out.Printf(ctx, "✔ Copied %s to clipboard. Will clear in %d seconds.", color.YellowString(name), timeout)
_ = notify.Notify(ctx, "gopass - clipboard", fmt.Sprintf("✔ Copied %s to clipboard. Will clear in %d seconds.", name, timeout))
out.Printf(ctx, "✔ Copied %s to clipboard. Will clearClip in %d seconds.", color.YellowString(name), timeout)
_ = notify.Notify(ctx, "gopass - clipboard", fmt.Sprintf("✔ Copied %s to clipboard. Will clearClip in %d seconds.", name, timeout))

return nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/clipboard/clipboard_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import (
"github.com/gopasspw/gopass/internal/pwschemes/argon2id"
)

// clear will spawn a copy of gopass that waits in a detached background
// clearClip will spawn a copy of gopass that waits in a detached background
// process group until the timeout is expired. It will then compare the contents
// of the clipboard and erase it if it still contains the data gopass copied
// to it.
func clear(ctx context.Context, name string, content []byte, timeout int) error {
func clearClip(ctx context.Context, name string, content []byte, timeout int) error {
hash, err := argon2id.Generate(string(content), 0)
if err != nil {
return fmt.Errorf("failed to generate checksum: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/clipboard/clipboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestUnsupportedCopyToClipboard(t *testing.T) {

func TestClearClipboard(t *testing.T) {
ctx, cancel := context.WithCancel(config.NewContextInMemory())
require.NoError(t, clear(ctx, "foo", []byte("bar"), 0))
require.NoError(t, clearClip(ctx, "foo", []byte("bar"), 0))
cancel()
time.Sleep(50 * time.Millisecond)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/clipboard/clipboard_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import (
"github.com/gopasspw/gopass/internal/pwschemes/argon2id"
)

// clear will spwan a copy of gopass that waits in a detached background
// clearClip will spwan a copy of gopass that waits in a detached background
// process group until the timeout is expired. It will then compare the contents
// of the clipboard and erase it if it still contains the data gopass copied
// to it.
func clear(ctx context.Context, name string, content []byte, timeout int) error {
func clearClip(ctx context.Context, name string, content []byte, timeout int) error {
hash, err := argon2id.Generate(string(content), 0)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/clipboard/copy_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func copyViaOsascript(ctx context.Context, password string) error {
"-e", "set type to current application's NSPasteboardTypeString",
// pb = a reference to the system's pasteboard
"-e", "set pb to current application's NSPasteboard's generalPasteboard()",
// Must clear contents before adding a new item to pasteboard
// Must clearClip contents before adding a new item to pasteboard
"-e", "pb's clearContents()",
// Set the flag ConcealedType so clipboard history managers don't record the password.
// The first argument can by anything, but an empty string will do fine.
Expand Down
Loading

0 comments on commit 4c2caf3

Please sign in to comment.