commit 8d33a511c1e08acddf0350bbe807818367b4b172
parent 84574dd5ca45921e63df738561bfe10f92ca2a76
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 10 Aug 2024 17:09:48 -0400
allow controlling modtime import
Diffstat:
8 files changed, 137 insertions(+), 21 deletions(-)
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -106,10 +106,16 @@ type (
CompletionsCommand string
CompletionsEnv string
ReKey struct {
- Store string
- KeyFile string
- Key string
- KeyMode string
+ Store string
+ KeyFile string
+ Key string
+ KeyMode string
+ ModMode string
+ ModModes struct {
+ Skip string
+ Error string
+ None string
+ }
}
}
)
@@ -227,6 +233,10 @@ func Usage(verbose bool, exe string) ([]string, error) {
document.ReKey.Key = setDocFlag(config.ReKeyKeyFlag)
document.ReKey.KeyMode = setDocFlag(config.ReKeyKeyModeFlag)
document.ReKey.KeyFile = setDocFlag(config.ReKeyKeyModeFlag)
+ document.ReKey.ModMode = setDocFlag(config.ReKeyModModeFlag)
+ document.ReKey.ModModes.None = config.ReKeyModModeNone
+ document.ReKey.ModModes.Skip = config.ReKeyModModeSkip
+ document.ReKey.ModModes.Error = config.ReKeyModModeError
files, err := docs.ReadDir(docDir)
if err != nil {
return nil, err
diff --git a/internal/app/core_test.go b/internal/app/core_test.go
@@ -13,7 +13,7 @@ func TestUsage(t *testing.T) {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = app.Usage(true, "lb")
- if len(u) != 107 {
+ if len(u) != 109 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/doc/rekey.txt b/internal/app/doc/rekey.txt
@@ -2,6 +2,10 @@ The password store can have the key (and file) changed via the '{{ $.ReKeyComman
subcommand. This command requires that '{{ $.ReKey.Store }}' is set and
a combination of new key settings are configured via '{{ $.ReKey.Key }}', '{{ $.ReKey.KeyMode }}', and '{{ $.ReKey.KeyFile }}' depending on the new database
credential preferences. The settings correspond to the 'LOCKBOX_'
-settings normally used when running `{{ $.Executable }}`.
+settings normally used when running `{{ $.Executable }}`. Additionally
+'{{ $.ReKey.ModMode }}' can be set to `{{ $.ReKey.ModModes.Skip }}` to skip
+modtime issues, `{{ $.ReKey.ModModes.None }}` to disable modtime imports, or
+`{{ $.ReKey.ModModes.Error }}` to ignore missing modification times (the
+default).
Note that is an advanced feature and should be used with caution/backups/etc.
diff --git a/internal/app/rekey.go b/internal/app/rekey.go
@@ -91,13 +91,25 @@ func ReKey(cmd CommandOptions, r Keyer) error {
if _, err := fmt.Fprintf(writer, "rekeying: %s\n", path); err != nil {
return err
}
- modTime := strings.TrimSpace(entry.ModTime)
- if modTime == "" {
- return errors.New("did not read modtime")
+ var modTime string
+ if vars.ModMode != config.ReKeyModModeNone {
+ modTime = strings.TrimSpace(entry.ModTime)
+ if modTime == "" {
+ switch vars.ModMode {
+ case config.ReKeyModModeSkip:
+ case config.ReKeyModModeError:
+ return errors.New("did not read modtime")
+ default:
+ return errors.New("unknown modtime control")
+ }
+ }
}
+
var insertEnv []string
- insertEnv = append(insertEnv, vars...)
- insertEnv = append(insertEnv, config.EnvModTime.KeyValue(modTime))
+ insertEnv = append(insertEnv, vars.Env...)
+ if modTime != "" {
+ insertEnv = append(insertEnv, config.EnvModTime.KeyValue(modTime))
+ }
if err := r.Insert(ReKeyEntry{Path: path, Env: insertEnv, Data: []byte(entry.Data)}); err != nil {
return err
}
diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go
@@ -3,6 +3,8 @@ package app_test
import (
"bytes"
"errors"
+ "fmt"
+ "strings"
"testing"
"github.com/seanenck/lockbox/internal/app"
@@ -13,8 +15,8 @@ type (
mockKeyer struct {
data map[string][]byte
err error
- rekeys int
items map[string]backend.JSON
+ rekeys [][]string
}
)
@@ -26,7 +28,7 @@ func (m *mockKeyer) JSON() (map[string]backend.JSON, error) {
}
func (m *mockKeyer) Insert(entry app.ReKeyEntry) error {
- m.rekeys++
+ m.rekeys = append(m.rekeys, entry.Env)
if entry.Path == "error" {
return errors.New("bad insert")
}
@@ -87,7 +89,66 @@ func TestReKey(t *testing.T) {
if cmd.buf.String() == "" {
t.Error("invalid data")
}
- if m.rekeys != 2 {
+ if len(m.rekeys) != 2 {
t.Errorf("invalid results")
}
+ obj := fmt.Sprintf("%v", m.rekeys)
+ for _, idx := range []int{1, 2} {
+ if !strings.Contains(obj, fmt.Sprintf("LOCKBOX_SET_MODTIME=%d", idx)) {
+ t.Errorf("missing converted modtime: %s", obj)
+ }
+ }
+}
+
+func modTimeKey(t *testing.T, mode string, modSet int) {
+ cmd := &mockCommand{}
+ cmd.confirm = true
+ cmd.buf = bytes.Buffer{}
+ cmd.args = []string{"-store", "store", "-key", "abc"}
+ m := &mockKeyer{}
+ m.items = map[string]backend.JSON{
+ "test1": {ModTime: "1"},
+ "test2": {ModTime: ""},
+ }
+ m.data = make(map[string][]byte)
+ m.data["test1"] = []byte{1}
+ m.data["test2"] = []byte{2}
+ cmd.buf = bytes.Buffer{}
+ cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", mode}
+ if err := app.ReKey(cmd, m); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ if len(m.rekeys) != 2 {
+ t.Errorf("invalid results")
+ }
+ if strings.Count(fmt.Sprintf("%v", m.rekeys), "LOCKBOX_SET_MODTIME=") != modSet {
+ t.Errorf("invalid object: %v", m.rekeys)
+ }
+}
+
+func TestReKeyModTime(t *testing.T) {
+ cmd := &mockCommand{}
+ cmd.confirm = true
+ cmd.buf = bytes.Buffer{}
+ cmd.args = []string{"-store", "store", "-key", "abc"}
+ m := &mockKeyer{}
+ m.items = map[string]backend.JSON{
+ "test1": {ModTime: "1"},
+ "test3": {ModTime: "a"},
+ "test2": {ModTime: ""},
+ }
+ m.data = make(map[string][]byte)
+ m.data["test1"] = []byte{1}
+ m.data["test2"] = []byte{2}
+ m.data["test3"] = []byte{4}
+ cmd.buf = bytes.Buffer{}
+ if err := app.ReKey(cmd, m); err == nil || err.Error() != "did not read modtime" {
+ t.Errorf("invalid error: %v", err)
+ }
+ cmd.args = []string{"-store", "store", "-key", "abc", "-modtime", "xyz"}
+ if err := app.ReKey(cmd, m); err == nil || err.Error() != "unknown modtime setting for import: xyz" {
+ t.Errorf("invalid error: %v", err)
+ }
+ modTimeKey(t, "none", 0)
+ modTimeKey(t, "skip", 1)
}
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -41,11 +41,19 @@ const (
ReKeyKeyFlag = "key"
// ReKeyKeyModeFlag is the flag used for rekey to set the key mode
ReKeyKeyModeFlag = "keymode"
+ // ReKeyModModeFlag indicates how to control modtime inserts
+ ReKeyModModeFlag = "modtime"
// sub categories
clipCategory keyCategory = "CLIP_"
totpCategory keyCategory = "TOTP_"
// YesValue are yes (on) values
YesValue = yes
+ // ReKeyModModeSkip will skip modtime import issues
+ ReKeyModModeSkip = "skip"
+ // ReKeyModModeNone will not attempt to import modtimes at all
+ ReKeyModModeNone = "none"
+ // ReKeyModModeError will error on modtime import issues
+ ReKeyModModeError = "error"
)
var (
@@ -114,6 +122,11 @@ type (
Env []string
Default bool
}
+ // ReKeyArgs are the arguments for rekeying
+ ReKeyArgs struct {
+ Env []string
+ ModMode string
+ }
)
func shlex(in string) ([]string, error) {
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"os"
+ "slices"
"sort"
"strings"
"time"
@@ -207,14 +208,19 @@ This value can NOT be an expansion itself.`,
)
// GetReKey will get the rekey environment settings
-func GetReKey(args []string) ([]string, error) {
+func GetReKey(args []string) (ReKeyArgs, error) {
set := flag.NewFlagSet("rekey", flag.ExitOnError)
store := set.String(ReKeyStoreFlag, "", "new store")
key := set.String(ReKeyKeyFlag, "", "new key")
keyFile := set.String(ReKeyKeyFileFlag, "", "new keyfile")
keyMode := set.String(ReKeyKeyModeFlag, "", "new keymode")
+ modSetting := set.String(ReKeyModModeFlag, ReKeyModModeError, "import setting for modtime")
if err := set.Parse(args); err != nil {
- return nil, err
+ return ReKeyArgs{}, err
+ }
+ mod := *modSetting
+ if !slices.Contains([]string{ReKeyModModeError, ReKeyModModeSkip, ReKeyModModeNone}, mod) {
+ return ReKeyArgs{}, fmt.Errorf("unknown modtime setting for import: %s", mod)
}
type keyer struct {
env EnvironmentString
@@ -234,10 +240,10 @@ func GetReKey(args []string) ([]string, error) {
out = append(out, k.env.KeyValue(k.in))
}
if !inStore.has || (!inKey.has && !inKeyFile.has) {
- return nil, fmt.Errorf("missing required arguments for rekey:\n -help for information on the flags or the lockbox help documentation for detailed usage")
+ return ReKeyArgs{}, fmt.Errorf("missing required arguments for rekey:\n -help for information on the flags or the lockbox help documentation for detailed usage")
}
sort.Strings(out)
- return out, nil
+ return ReKeyArgs{Env: out, ModMode: mod}, nil
}
// ListEnvironmentVariables will print information about env variables
diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go
@@ -107,20 +107,30 @@ func TestReKey(t *testing.T) {
if err == nil || !strings.HasPrefix(err.Error(), "missing required arguments for rekey") {
t.Errorf("failed: %v", err)
}
+ _, err = config.GetReKey([]string{"-store", "abc", "-key", "aaa", "-modtime", "xyz"})
+ if err == nil || !strings.HasPrefix(err.Error(), "unknown modtime setting for import: xyz") {
+ t.Errorf("failed: %v", err)
+ }
out, err := config.GetReKey([]string{"-store", "abc", "-key", "aaa"})
if err != nil {
t.Errorf("failed: %v", err)
}
- if fmt.Sprintf("%v", out) != "[LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" {
+ if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY=aaa LOCKBOX_KEYFILE= LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" {
t.Errorf("invalid env: %v", out)
}
- out, err = config.GetReKey([]string{"-store", "abc", "-keyfile", "aaa"})
+ if out.ModMode != "error" {
+ t.Errorf("invalid modtime setting: %v", out)
+ }
+ out, err = config.GetReKey([]string{"-store", "abc", "-keyfile", "aaa", "-modtime", "none"})
if err != nil {
t.Errorf("failed: %v", err)
}
- if fmt.Sprintf("%v", out) != "[LOCKBOX_KEY= LOCKBOX_KEYFILE=aaa LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" {
+ if fmt.Sprintf("%v", out.Env) != "[LOCKBOX_KEY= LOCKBOX_KEYFILE=aaa LOCKBOX_KEYMODE= LOCKBOX_STORE=abc]" {
t.Errorf("invalid env: %v", out)
}
+ if out.ModMode != "none" {
+ t.Errorf("invalid modtime setting: %v", out)
+ }
os.Setenv("LOCKBOX_KEY_NEW", "")
os.Setenv("LOCKBOX_STORE_NEW", "")
os.Setenv("LOCKBOX_KEY_NEW", "")