1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package authn
16
17 import (
18 "os"
19 "path/filepath"
20 "sync"
21 "time"
22
23 "github.com/docker/cli/cli/config"
24 "github.com/docker/cli/cli/config/configfile"
25 "github.com/docker/cli/cli/config/types"
26 "github.com/google/go-containerregistry/pkg/name"
27 "github.com/mitchellh/go-homedir"
28 )
29
30
31 type Resource interface {
32
33
34 String() string
35
36
37
38
39 RegistryStr() string
40 }
41
42
43 type Keychain interface {
44
45 Resolve(Resource) (Authenticator, error)
46 }
47
48
49
50 type defaultKeychain struct {
51 mu sync.Mutex
52 }
53
54 var (
55
56 DefaultKeychain = &defaultKeychain{}
57 )
58
59 const (
60
61
62 DefaultAuthKey = "https://" + name.DefaultRegistry + "/v1/"
63 )
64
65
66 func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
67 dk.mu.Lock()
68 defer dk.mu.Unlock()
69
70
71
72
73
74
75
76 foundDockerConfig := false
77 home, err := homedir.Dir()
78 if err == nil {
79 foundDockerConfig = fileExists(filepath.Join(home, ".docker/config.json"))
80 }
81
82 if !foundDockerConfig && os.Getenv("DOCKER_CONFIG") != "" {
83 foundDockerConfig = fileExists(filepath.Join(os.Getenv("DOCKER_CONFIG"), "config.json"))
84 }
85
86
87
88
89
90
91
92
93 var cf *configfile.ConfigFile
94 if foundDockerConfig {
95 cf, err = config.Load(os.Getenv("DOCKER_CONFIG"))
96 if err != nil {
97 return nil, err
98 }
99 } else {
100 f, err := os.Open(filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "containers/auth.json"))
101 if err != nil {
102 return Anonymous, nil
103 }
104 defer f.Close()
105 cf, err = config.LoadFromReader(f)
106 if err != nil {
107 return nil, err
108 }
109 }
110
111
112
113
114 var cfg, empty types.AuthConfig
115 for _, key := range []string{
116 target.String(),
117 target.RegistryStr(),
118 } {
119 if key == name.DefaultRegistry {
120 key = DefaultAuthKey
121 }
122
123 cfg, err = cf.GetAuthConfig(key)
124 if err != nil {
125 return nil, err
126 }
127
128
129
130 cfg.ServerAddress = ""
131 if cfg != empty {
132 break
133 }
134 }
135 if cfg == empty {
136 return Anonymous, nil
137 }
138
139 return FromConfig(AuthConfig{
140 Username: cfg.Username,
141 Password: cfg.Password,
142 Auth: cfg.Auth,
143 IdentityToken: cfg.IdentityToken,
144 RegistryToken: cfg.RegistryToken,
145 }), nil
146 }
147
148
149 func fileExists(path string) bool {
150 fi, err := os.Stat(path)
151 return err == nil && !fi.IsDir()
152 }
153
154
155
156
157
158
159 type Helper interface {
160 Get(serverURL string) (string, string, error)
161 }
162
163
164
165
166 func NewKeychainFromHelper(h Helper) Keychain { return wrapper{h} }
167
168 type wrapper struct{ h Helper }
169
170 func (w wrapper) Resolve(r Resource) (Authenticator, error) {
171 u, p, err := w.h.Get(r.RegistryStr())
172 if err != nil {
173 return Anonymous, nil
174 }
175
176
177 if u == "<token>" {
178 return FromConfig(AuthConfig{Username: u, IdentityToken: p}), nil
179 }
180 return FromConfig(AuthConfig{Username: u, Password: p}), nil
181 }
182
183 func RefreshingKeychain(inner Keychain, duration time.Duration) Keychain {
184 return &refreshingKeychain{
185 keychain: inner,
186 duration: duration,
187 }
188 }
189
190 type refreshingKeychain struct {
191 keychain Keychain
192 duration time.Duration
193 clock func() time.Time
194 }
195
196 func (r *refreshingKeychain) Resolve(target Resource) (Authenticator, error) {
197 last := time.Now()
198 auth, err := r.keychain.Resolve(target)
199 if err != nil || auth == Anonymous {
200 return auth, err
201 }
202 return &refreshing{
203 target: target,
204 keychain: r.keychain,
205 last: last,
206 cached: auth,
207 duration: r.duration,
208 clock: r.clock,
209 }, nil
210 }
211
212 type refreshing struct {
213 sync.Mutex
214 target Resource
215 keychain Keychain
216
217 duration time.Duration
218
219 last time.Time
220 cached Authenticator
221
222
223 clock func() time.Time
224 }
225
226 func (r *refreshing) Authorization() (*AuthConfig, error) {
227 r.Lock()
228 defer r.Unlock()
229 if r.cached == nil || r.expired() {
230 r.last = r.now()
231 auth, err := r.keychain.Resolve(r.target)
232 if err != nil {
233 return nil, err
234 }
235 r.cached = auth
236 }
237 return r.cached.Authorization()
238 }
239
240 func (r *refreshing) now() time.Time {
241 if r.clock == nil {
242 return time.Now()
243 }
244 return r.clock()
245 }
246
247 func (r *refreshing) expired() bool {
248 return r.now().Sub(r.last) > r.duration
249 }
250
View as plain text