Compare commits
1
Commits
next
..
746a433ef3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
746a433ef3 |
@@ -17,11 +17,6 @@ https://git.eeqj.de/sneak/secret/milestone/12
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-10-15: The comment on `commandWait` in `internal/cli/integration_test.go`
|
|
||||||
no longer says `script/test` gives the package 30 seconds
|
|
||||||
(https://git.eeqj.de/sneak/secret/issues/195). `script/test` sets no time
|
|
||||||
limit; the test phase of the `Dockerfile` sets one with `go test -timeout`.
|
|
||||||
The comment now names that place instead of a number.
|
|
||||||
- 2026-10-15: The canonical files are re-vendored from `sneak/prompts` commit
|
- 2026-10-15: The canonical files are re-vendored from `sneak/prompts` commit
|
||||||
`e3f79c4` (https://git.eeqj.de/sneak/secret/issues/129), so Go's build cache
|
`e3f79c4` (https://git.eeqj.de/sneak/secret/issues/129), so Go's build cache
|
||||||
survives between check builds and `make test` no longer compiles the standard
|
survives between check builds and `make test` no longer compiles the standard
|
||||||
@@ -34,15 +29,6 @@ https://git.eeqj.de/sneak/secret/milestone/12
|
|||||||
no image. The CI workflow cancels a branch's older run, stops after 20
|
no image. The CI workflow cancels a branch's older run, stops after 20
|
||||||
minutes, fetches the tags and leaves no token behind. `script/bootstrap` uses
|
minutes, fetches the tags and leaves no token behind. `script/bootstrap` uses
|
||||||
the installed node only when its major version is the pinned one.
|
the installed node only when its major version is the pinned one.
|
||||||
- 2026-10-15: `make test` no longer fails on some runs in `internal/cli`
|
|
||||||
(https://git.eeqj.de/sneak/secret/issues/192). `cli.Entry()` set `time.Local`
|
|
||||||
to UTC and ended by wiping every memguard buffer, and a test ran it in the
|
|
||||||
same process as the other tests. A timer left by an earlier test could read
|
|
||||||
`time.Local` as it changed, which the race detector reported, and the wipe
|
|
||||||
could destroy a buffer still in use, the likeliest cause of a crash of the
|
|
||||||
test process on another run. `main()` in `cmd/secret` now sets `time.Local`
|
|
||||||
before it calls `cli.Entry()`, and that test runs `cli.Entry()` in a process
|
|
||||||
of its own.
|
|
||||||
- 2026-10-15: `secret mv` from one vault to another unlocks both vaults before
|
- 2026-10-15: `secret mv` from one vault to another unlocks both vaults before
|
||||||
it takes the state directory lock
|
it takes the state directory lock
|
||||||
(https://git.eeqj.de/sneak/secret/issues/189). It unlocked them while holding
|
(https://git.eeqj.de/sneak/secret/issues/189). It unlocked them while holding
|
||||||
|
|||||||
@@ -3,16 +3,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"sneak.berlin/go/secret/internal/cli"
|
"sneak.berlin/go/secret/internal/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Every time secret prints or writes is in UTC, whatever TZ or the
|
|
||||||
// system's time zone is set to. It is set here, before cli.Entry starts
|
|
||||||
// any goroutine that could read time.Local while it changes.
|
|
||||||
time.Local = time.UTC //nolint:gosmopolitan // this makes local time UTC
|
|
||||||
|
|
||||||
os.Exit(cli.Entry())
|
os.Exit(cli.Entry())
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-51
@@ -3,7 +3,6 @@ package cli_test
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -17,67 +16,29 @@ import (
|
|||||||
"sneak.berlin/go/secret/internal/secret"
|
"sneak.berlin/go/secret/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// envRunEntry, when set, makes this test binary run cli.Entry with its
|
|
||||||
// own arguments and exit, instead of running the tests.
|
|
||||||
envRunEntry = "SECRET_TEST_RUN_ENTRY"
|
|
||||||
|
|
||||||
// bufferWiped is what the test binary prints, run with envRunEntry set,
|
|
||||||
// when cli.Entry wiped the buffer made before it ran.
|
|
||||||
bufferWiped = "Entry wiped the buffer made before it ran"
|
|
||||||
)
|
|
||||||
|
|
||||||
// With envRunEntry set, the test binary makes a buffer, runs cli.Entry,
|
|
||||||
// says whether the buffer is wiped, and exits with Entry's exit code. This
|
|
||||||
// is done in init, not in a test, so that TestMain does not run in that
|
|
||||||
// process: it would build the secret binary again, and remove it at the end,
|
|
||||||
// while the other tests run it.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoinits // must run before TestMain, as above
|
|
||||||
func init() {
|
|
||||||
if os.Getenv(envRunEntry) == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := memguard.NewBufferFromBytes([]byte("key material"))
|
|
||||||
code := cli.Entry()
|
|
||||||
|
|
||||||
if !buf.IsAlive() {
|
|
||||||
fmt.Fprintln(os.Stderr, bufferWiped)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry must return its exit code rather than exit, so that its deferred
|
// Entry must return its exit code rather than exit, so that its deferred
|
||||||
// memguard purge runs on the success and the error path alike. Entry wipes
|
// memguard purge runs on the success and the error path alike.
|
||||||
// every memguard buffer in the process, including those other tests are
|
//
|
||||||
// using, so it runs in a process of its own: this test binary, run again
|
//nolint:paralleltest // sets os.Args, and Entry wipes every buffer in the process
|
||||||
// with envRunEntry set.
|
|
||||||
func TestEntryWipesBuffersAndReturnsExitCode(t *testing.T) {
|
func TestEntryWipesBuffersAndReturnsExitCode(t *testing.T) {
|
||||||
t.Parallel()
|
savedArgs := os.Args
|
||||||
|
|
||||||
|
t.Cleanup(func() { os.Args = savedArgs })
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
args []string
|
args []string
|
||||||
exitCode int
|
exitCode int
|
||||||
}{
|
}{
|
||||||
{args: []string{"--help"}, exitCode: 0},
|
{args: []string{"secret", "--help"}, exitCode: 0},
|
||||||
{args: []string{"no-such-command"}, exitCode: 1},
|
{args: []string{"secret", "no-such-command"}, exitCode: 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
//nolint:gosec // G204: test runs its own test binary
|
buf := memguard.NewBufferFromBytes([]byte("key material"))
|
||||||
cmd := exec.CommandContext(t.Context(), os.Args[0], tt.args...)
|
os.Args = tt.args
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(), envRunEntry+"=1")
|
assert.Equal(t, tt.exitCode, cli.Entry(), "exit code for %v", tt.args)
|
||||||
|
assert.False(t, buf.IsAlive(), "Entry left a buffer unwiped for %v", tt.args)
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
require.NotNil(t, cmd.ProcessState, "%v did not run: %v", tt.args, err)
|
|
||||||
|
|
||||||
assert.Equal(t, tt.exitCode, cmd.ProcessState.ExitCode(),
|
|
||||||
"exit code for %v: %s", tt.args, output)
|
|
||||||
assert.Contains(t, string(output), bufferWiped,
|
|
||||||
"Entry left a buffer unwiped for %v", tt.args)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,9 @@ const (
|
|||||||
testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||||
|
|
||||||
// commandWait is how long a test lets the secret binary run before it
|
// commandWait is how long a test lets the secret binary run before it
|
||||||
// kills it and fails. It stays well under the time limit the test phase
|
// kills it and fails. It stays well under the 30 seconds script/test
|
||||||
// of the Dockerfile gives the whole package (go test -timeout), so a
|
// gives the whole package, so a command that hangs fails the test with
|
||||||
// command that hangs fails the test with the test's own message instead
|
// the test's own message instead of Go's timeout panic.
|
||||||
// of Go's timeout panic.
|
|
||||||
commandWait = 10 * time.Second
|
commandWait = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/awnumar/memguard"
|
"github.com/awnumar/memguard"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -14,6 +15,10 @@ import (
|
|||||||
// every memguard buffer before it returns, so the caller must do nothing
|
// every memguard buffer before it returns, so the caller must do nothing
|
||||||
// but exit with the code.
|
// but exit with the code.
|
||||||
func Entry() int {
|
func Entry() int {
|
||||||
|
// Every time secret prints or writes is in UTC, whatever TZ or the
|
||||||
|
// system's time zone is set to.
|
||||||
|
time.Local = time.UTC //nolint:gosmopolitan // this makes local time UTC
|
||||||
|
|
||||||
raiseLockedMemoryLimit()
|
raiseLockedMemoryLimit()
|
||||||
|
|
||||||
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
|
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
|
||||||
|
|||||||
Reference in New Issue
Block a user