-
-
Notifications
You must be signed in to change notification settings - Fork 496
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add gopass merge Add a subcommand to implement a merge workflow. This command accepts multiple entries to be merged into one to help deduplicating secrets. Fixes #1948 RELEASE_NOTES=[ENHACNEMENT] Add gopass merge Signed-off-by: Dominik Schulz <[email protected]> * Add subcommand documentation. RELEASE_NOTES=n/a Signed-off-by: Dominik Schulz <[email protected]> * Update tests RELEASE_NOTES=n/a Signed-off-by: Dominik Schulz <[email protected]> * Fix queue bugs. RELEASE_NOTES=n/a Signed-off-by: Dominik Schulz <[email protected]>
- Loading branch information
1 parent
0aded87
commit d81ebf6
Showing
6 changed files
with
173 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package action | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/gopasspw/gopass/internal/audit" | ||
"github.com/gopasspw/gopass/internal/editor" | ||
"github.com/gopasspw/gopass/internal/out" | ||
"github.com/gopasspw/gopass/internal/queue" | ||
"github.com/gopasspw/gopass/pkg/ctxutil" | ||
"github.com/gopasspw/gopass/pkg/debug" | ||
"github.com/gopasspw/gopass/pkg/gopass/secrets" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
// Merge implements the merge subcommand that allows merging multiple entries. | ||
func (s *Action) Merge(c *cli.Context) error { | ||
ctx := ctxutil.WithGlobalFlags(c) | ||
to := c.Args().First() | ||
from := c.Args().Tail() | ||
|
||
if to == "" { | ||
return ExitError(ExitUsage, nil, "usage: %s merge <to> <from> [<from>]", s.Name) | ||
} | ||
if len(from) < 1 { | ||
return ExitError(ExitUsage, nil, "usage: %s merge <to> <from> [<from>]", s.Name) | ||
} | ||
|
||
ed := editor.Path(c) | ||
if err := editor.Check(ctx, ed); err != nil { | ||
out.Warningf(ctx, "Failed to check editor config: %s", err) | ||
} | ||
|
||
content := &bytes.Buffer{} | ||
for _, k := range c.Args().Slice() { | ||
if !s.Store.Exists(ctx, k) { | ||
continue | ||
} | ||
sec, err := s.Store.Get(ctxutil.WithShowParsing(ctx, false), k) | ||
if err != nil { | ||
return ExitError(ExitDecrypt, err, "failed to decrypt: %s: %s", k, err) | ||
} | ||
_, err = content.WriteString("\n# Secret: " + k + "\n") | ||
if err != nil { | ||
return ExitError(ExitUnknown, err, "failed to write: %s", err) | ||
} | ||
_, err = content.Write(sec.Bytes()) | ||
if err != nil { | ||
return ExitError(ExitUnknown, err, "failed to write: %s", err) | ||
} | ||
} | ||
|
||
newContent := content.Bytes() | ||
if !c.Bool("force") { | ||
var err error | ||
// invoke the editor to let the user edit the content | ||
newContent, err = editor.Invoke(ctx, ed, content.Bytes()) | ||
if err != nil { | ||
return ExitError(ExitUnknown, err, "failed to invoke editor: %s", err) | ||
} | ||
|
||
// If content is equal, nothing changed, exiting | ||
if bytes.Equal(content.Bytes(), newContent) { | ||
return nil | ||
} | ||
} | ||
|
||
nSec := secrets.ParsePlain(newContent) | ||
|
||
// if the secret has a password, we check it's strength | ||
if pw := nSec.Password(); pw != "" && !c.Bool("force") { | ||
audit.Single(ctx, pw) | ||
} | ||
|
||
// write result (back) to store | ||
if err := s.Store.Set(ctxutil.WithCommitMessage(ctx, fmt.Sprintf("Merged %+v", c.Args().Slice())), to, nSec); err != nil { | ||
return ExitError(ExitEncrypt, err, "failed to encrypt secret %s: %s", to, err) | ||
} | ||
|
||
if !c.Bool("delete") { | ||
return nil | ||
} | ||
|
||
// wait until the previous commit is done | ||
// TODO: This wouldn't be necessary if we could handle merging and deleting | ||
// in a single commit, but then we'd need to expose additional implementation | ||
// details of the underlying VCS. Or create some kind of transaction on top | ||
// of the Git wrapper. | ||
if err := queue.GetQueue(ctx).Idle(time.Minute); err != nil { | ||
return err | ||
} | ||
|
||
for _, old := range from { | ||
if !s.Store.Exists(ctx, old) { | ||
continue | ||
} | ||
debug.Log("deleting merged entry %s", old) | ||
if err := s.Store.Delete(ctx, old); err != nil { | ||
return ExitError(ExitUnknown, err, "failed to delete %s: %s", old, err) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters