-
-
Notifications
You must be signed in to change notification settings - Fork 479
/
reencrypt.go
131 lines (109 loc) · 3.32 KB
/
reencrypt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package leaf
import (
"context"
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/termio"
)
// nolint:ifshort
// reencrypt will re-encrypt all entries for the current recipients.
func (s *Store) reencrypt(ctx context.Context) error {
entries, err := s.List(ctx, "")
if err != nil {
return fmt.Errorf("failed to list store: %w", err)
}
// Most gnupg setups don't work well with concurrency > 1, but
// for other backends - e.g. age - this could very well be > 1.
conc := s.crypto.Concurrency()
// save original value of auto push
{
// shadow ctx in this block only
ctx := ctxutil.WithGitCommit(ctx, false)
// progress bar
bar := termio.NewProgressBar(int64(len(entries)))
bar.Hidden = !ctxutil.IsTerminal(ctx) || ctxutil.IsHidden(ctx)
var wg sync.WaitGroup
jobs := make(chan string)
// We use a logger to write without race condition on stdout
logger := log.New(os.Stdout, "", 0)
out.Print(ctx, "Starting reencrypt")
for i := 0; i < conc; i++ {
wg.Add(1) // we start a new job
go func(workerId int) {
// the workers are fed through an unbuffered channel
for e := range jobs {
content, err := s.Get(ctx, e)
if err != nil {
logger.Printf("Worker %d: Failed to get current value for %s: %s\n", workerId, e, err)
continue
}
if err := s.Set(WithNoGitOps(ctx, conc > 1), e, content); err != nil {
if !errors.Is(err, store.ErrMeaninglessWrite) {
logger.Printf("Worker %d: Failed to write %s: %s\n", workerId, e, err)
continue
}
logger.Printf("Worker %d: Writing secret %s is not needed\n", workerId, e)
}
}
wg.Done() // report the job as finished
}(i)
}
for _, e := range entries {
// check for context cancellation
select {
case <-ctx.Done():
// We close the channel, so the worker will terminate
close(jobs)
// we wait for all workers to have finished
wg.Wait()
return fmt.Errorf("context canceled")
default:
}
if bar != nil {
bar.Inc()
}
e = strings.TrimPrefix(e, s.alias)
jobs <- e
}
// We close the channel, so the workers will terminate
close(jobs)
// we wait for all workers to have finished
wg.Wait()
bar.Done()
}
// if we are working concurrently, we cannot git add during the process
// to avoid a race condition on git .index.lock file, so we do it now.
if conc > 1 {
for _, name := range entries {
p := s.Passfile(name)
if err := s.storage.TryAdd(ctx, p); err != nil {
return fmt.Errorf("failed to add %q to git: %w", p, err)
}
debug.Log("added %s to git", p)
}
}
if err := s.storage.TryCommit(ctx, ctxutil.GetCommitMessage(ctx)); err != nil {
return fmt.Errorf("failed to commit changes to git: %w", err)
}
return s.reencryptGitPush(ctx)
}
func (s *Store) reencryptGitPush(ctx context.Context) error {
ctx = config.WithMount(ctx, s.alias)
if !config.Bool(ctx, "core.autopush") {
debug.Log("not pushing to git remote, core.autopush is false")
return nil
}
if err := s.storage.TryPush(ctx, "", ""); err != nil {
return fmt.Errorf("failed to push change to git remote: %w", err)
}
return nil
}