5
5
package pass
6
6
7
7
import (
8
- "bytes"
9
8
"encoding/base64"
10
9
"errors"
11
10
"fmt"
@@ -14,40 +13,63 @@ import (
14
13
"os/exec"
15
14
"path"
16
15
"strings"
16
+ "sync"
17
17
18
18
"github.com/docker/docker-credential-helpers/credentials"
19
19
)
20
20
21
21
const PASS_FOLDER = "docker-credential-helpers"
22
22
23
- var (
24
- PassInitialized bool
25
- )
23
+ // Pass handles secrets using Linux secret-service as a store.
24
+ type Pass struct {}
26
25
27
- func init () {
26
+ // Ideally these would be stored as members of Pass, but since all of Pass's
27
+ // methods have value receivers, not pointer receivers, and changing that is
28
+ // backwards incompatible, we assume that all Pass instances share the same configuration
29
+
30
+ // initializationMutex is held while initializing so that only one 'pass'
31
+ // round-tripping is done to check pass is functioning.
32
+ var initializationMutex sync.Mutex
33
+ var passInitialized bool
34
+
35
+ func (p Pass ) checkInitialized () error {
36
+ initializationMutex .Lock ()
37
+ defer initializationMutex .Unlock ()
38
+ if passInitialized {
39
+ return nil
40
+ }
28
41
// In principle, we could just run `pass init`. However, pass has a bug
29
42
// where if gpg fails, it doesn't always exit 1. Additionally, pass
30
43
// uses gpg2, but gpg is the default, which may be confusing. So let's
31
44
// just explictily check that pass actually can store and retreive a
32
45
// password.
33
46
password := "pass is initialized"
34
- name := path .Join (PASS_FOLDER , "docker-pass-initialized-check" )
47
+ name := path .Join (getPassDir () , "docker-pass-initialized-check" )
35
48
36
- _ , err := runPass (password , "insert" , "-f" , "-m" , name )
49
+ _ , err := p . runPassHelper (password , "insert" , "-f" , "-m" , name )
37
50
if err != nil {
38
- return
51
+ return fmt . Errorf ( "error initializing pass: %v" , err )
39
52
}
40
53
41
- stored , err := runPass ("" , "show" , name )
42
- PassInitialized = err == nil && stored == password
54
+ stored , err := p .runPassHelper ("" , "show" , name )
55
+ if err != nil {
56
+ return fmt .Errorf ("error fetching password during initialization: %v" , err )
57
+ }
58
+ if stored != password {
59
+ return fmt .Errorf ("error round-tripping password during initialization: %q != %q" , password , stored )
60
+ }
61
+ passInitialized = true
62
+ return nil
63
+ }
43
64
44
- if PassInitialized {
45
- runPass ("" , "rm" , "-rf" , name )
65
+ func (p Pass ) runPass (stdinContent string , args ... string ) (string , error ) {
66
+ if err := p .checkInitialized (); err != nil {
67
+ return "" , err
46
68
}
69
+ return p .runPassHelper (stdinContent , args ... )
47
70
}
48
71
49
- func runPass (stdin string , args ... string ) (string , error ) {
50
- var stdout , stderr bytes.Buffer
72
+ func (p Pass ) runPassHelper (stdinContent string , args ... string ) (string , error ) {
51
73
cmd := exec .Command ("pass" , args ... )
52
74
cmd .Stdin = strings .NewReader (stdin )
53
75
cmd .Stdout = & stdout
@@ -61,37 +83,26 @@ func runPass(stdin string, args ...string) (string, error) {
61
83
return stdout .String (), nil
62
84
}
63
85
64
- // Pass handles secrets using Linux secret-service as a store.
65
- type Pass struct {}
66
-
67
86
// Add adds new credentials to the keychain.
68
87
func (h Pass ) Add (creds * credentials.Credentials ) error {
69
- if ! PassInitialized {
70
- return errors .New ("pass store is uninitialized" )
71
- }
72
-
73
88
if creds == nil {
74
89
return errors .New ("missing credentials" )
75
90
}
76
91
77
92
encoded := base64 .URLEncoding .EncodeToString ([]byte (creds .ServerURL ))
78
93
79
- _ , err := runPass (creds .Secret , "insert" , "-f" , "-m" , path .Join (PASS_FOLDER , encoded , creds .Username ))
94
+ _ , err := h . runPass (creds .Secret , "insert" , "-f" , "-m" , path .Join (PASS_FOLDER , encoded , creds .Username ))
80
95
return err
81
96
}
82
97
83
98
// Delete removes credentials from the store.
84
99
func (h Pass ) Delete (serverURL string ) error {
85
- if ! PassInitialized {
86
- return errors .New ("pass store is uninitialized" )
87
- }
88
-
89
100
if serverURL == "" {
90
101
return errors .New ("missing server url" )
91
102
}
92
103
93
104
encoded := base64 .URLEncoding .EncodeToString ([]byte (serverURL ))
94
- _ , err := runPass ("" , "rm" , "-rf" , path .Join (PASS_FOLDER , encoded ))
105
+ _ , err := h . runPass ("" , "rm" , "-rf" , path .Join (PASS_FOLDER , encoded ))
95
106
return err
96
107
}
97
108
@@ -123,10 +134,6 @@ func listPassDir(args ...string) ([]os.FileInfo, error) {
123
134
124
135
// Get returns the username and secret to use for a given registry server URL.
125
136
func (h Pass ) Get (serverURL string ) (string , string , error ) {
126
- if ! PassInitialized {
127
- return "" , "" , errors .New ("pass store is uninitialized" )
128
- }
129
-
130
137
if serverURL == "" {
131
138
return "" , "" , errors .New ("missing server url" )
132
139
}
@@ -151,16 +158,12 @@ func (h Pass) Get(serverURL string) (string, string, error) {
151
158
}
152
159
153
160
actual := strings .TrimSuffix (usernames [0 ].Name (), ".gpg" )
154
- secret , err := runPass ("" , "show" , path .Join (PASS_FOLDER , encoded , actual ))
161
+ secret , err := h . runPass ("" , "show" , path .Join (PASS_FOLDER , encoded , actual ))
155
162
return actual , secret , err
156
163
}
157
164
158
165
// List returns the stored URLs and corresponding usernames for a given credentials label
159
166
func (h Pass ) List () (map [string ]string , error ) {
160
- if ! PassInitialized {
161
- return nil , errors .New ("pass store is uninitialized" )
162
- }
163
-
164
167
servers , err := listPassDir ()
165
168
if err != nil {
166
169
return nil , err
0 commit comments