1
16
17 package credentialprovider
18
19 import (
20 "encoding/base64"
21 "encoding/json"
22 "errors"
23 "fmt"
24 "io"
25 "net/http"
26 "os"
27 "path/filepath"
28 "strings"
29 "sync"
30
31 "k8s.io/klog/v2"
32 )
33
34 const (
35 maxReadLength = 10 * 1 << 20
36 )
37
38
39
40 type DockerConfigJSON struct {
41 Auths DockerConfig `json:"auths"`
42
43 HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
44 }
45
46
47
48
49 type DockerConfig map[string]DockerConfigEntry
50
51
52 type DockerConfigEntry struct {
53 Username string
54 Password string
55 Email string
56 Provider DockerConfigProvider
57 }
58
59 var (
60 preferredPathLock sync.Mutex
61 preferredPath = ""
62 workingDirPath = ""
63 homeDirPath, _ = os.UserHomeDir()
64 rootDirPath = "/"
65 homeJSONDirPath = filepath.Join(homeDirPath, ".docker")
66 rootJSONDirPath = filepath.Join(rootDirPath, ".docker")
67
68 configFileName = ".dockercfg"
69 configJSONFileName = "config.json"
70 )
71
72
73 func SetPreferredDockercfgPath(path string) {
74 preferredPathLock.Lock()
75 defer preferredPathLock.Unlock()
76 preferredPath = path
77 }
78
79
80 func GetPreferredDockercfgPath() string {
81 preferredPathLock.Lock()
82 defer preferredPathLock.Unlock()
83 return preferredPath
84 }
85
86
87 func DefaultDockercfgPaths() []string {
88 return []string{GetPreferredDockercfgPath(), workingDirPath, homeDirPath, rootDirPath}
89 }
90
91
92 func DefaultDockerConfigJSONPaths() []string {
93 return []string{GetPreferredDockercfgPath(), workingDirPath, homeJSONDirPath, rootJSONDirPath}
94 }
95
96
97
98 func ReadDockercfgFile(searchPaths []string) (cfg DockerConfig, err error) {
99 if len(searchPaths) == 0 {
100 searchPaths = DefaultDockercfgPaths()
101 }
102
103 for _, configPath := range searchPaths {
104 absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configFileName))
105 if err != nil {
106 klog.Errorf("while trying to canonicalize %s: %v", configPath, err)
107 continue
108 }
109 klog.V(4).Infof("looking for .dockercfg at %s", absDockerConfigFileLocation)
110 contents, err := os.ReadFile(absDockerConfigFileLocation)
111 if os.IsNotExist(err) {
112 continue
113 }
114 if err != nil {
115 klog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
116 continue
117 }
118 cfg, err := ReadDockerConfigFileFromBytes(contents)
119 if err != nil {
120 klog.V(4).Infof("couldn't get the config from %q contents: %v", absDockerConfigFileLocation, err)
121 continue
122 }
123
124 klog.V(4).Infof("found .dockercfg at %s", absDockerConfigFileLocation)
125 return cfg, nil
126
127 }
128 return nil, fmt.Errorf("couldn't find valid .dockercfg after checking in %v", searchPaths)
129 }
130
131
132
133 func ReadDockerConfigJSONFile(searchPaths []string) (cfg DockerConfig, err error) {
134 if len(searchPaths) == 0 {
135 searchPaths = DefaultDockerConfigJSONPaths()
136 }
137 for _, configPath := range searchPaths {
138 absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configJSONFileName))
139 if err != nil {
140 klog.Errorf("while trying to canonicalize %s: %v", configPath, err)
141 continue
142 }
143 klog.V(4).Infof("looking for %s at %s", configJSONFileName, absDockerConfigFileLocation)
144 cfg, err = ReadSpecificDockerConfigJSONFile(absDockerConfigFileLocation)
145 if err != nil {
146 if !os.IsNotExist(err) {
147 klog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
148 }
149 continue
150 }
151 klog.V(4).Infof("found valid %s at %s", configJSONFileName, absDockerConfigFileLocation)
152 return cfg, nil
153 }
154 return nil, fmt.Errorf("couldn't find valid %s after checking in %v", configJSONFileName, searchPaths)
155
156 }
157
158
159 func ReadSpecificDockerConfigJSONFile(filePath string) (cfg DockerConfig, err error) {
160 var contents []byte
161
162 if contents, err = os.ReadFile(filePath); err != nil {
163 return nil, err
164 }
165 return readDockerConfigJSONFileFromBytes(contents)
166 }
167
168
169 func ReadDockerConfigFile() (cfg DockerConfig, err error) {
170 if cfg, err := ReadDockerConfigJSONFile(nil); err == nil {
171 return cfg, nil
172 }
173
174 return ReadDockercfgFile(nil)
175 }
176
177
178 type HTTPError struct {
179 StatusCode int
180 URL string
181 }
182
183
184 func (he *HTTPError) Error() string {
185 return fmt.Sprintf("http status code: %d while fetching url %s",
186 he.StatusCode, he.URL)
187 }
188
189
190 func ReadURL(url string, client *http.Client, header *http.Header) (body []byte, err error) {
191 req, err := http.NewRequest("GET", url, nil)
192 if err != nil {
193 return nil, err
194 }
195 if header != nil {
196 req.Header = *header
197 }
198 resp, err := client.Do(req)
199 if err != nil {
200 return nil, err
201 }
202 defer resp.Body.Close()
203
204 if resp.StatusCode != http.StatusOK {
205 klog.V(2).InfoS("Failed to read URL", "statusCode", resp.StatusCode, "URL", url)
206 return nil, &HTTPError{
207 StatusCode: resp.StatusCode,
208 URL: url,
209 }
210 }
211
212 limitedReader := &io.LimitedReader{R: resp.Body, N: maxReadLength}
213 contents, err := io.ReadAll(limitedReader)
214 if err != nil {
215 return nil, err
216 }
217
218 if limitedReader.N <= 0 {
219 return nil, errors.New("the read limit is reached")
220 }
221
222 return contents, nil
223 }
224
225
226 func ReadDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
227 if err = json.Unmarshal(contents, &cfg); err != nil {
228 return nil, errors.New("error occurred while trying to unmarshal json")
229 }
230 return
231 }
232
233 func readDockerConfigJSONFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
234 var cfgJSON DockerConfigJSON
235 if err = json.Unmarshal(contents, &cfgJSON); err != nil {
236 return nil, errors.New("error occurred while trying to unmarshal json")
237 }
238 cfg = cfgJSON.Auths
239 return
240 }
241
242
243
244 type dockerConfigEntryWithAuth struct {
245
246 Username string `json:"username,omitempty"`
247
248 Password string `json:"password,omitempty"`
249
250 Email string `json:"email,omitempty"`
251
252 Auth string `json:"auth,omitempty"`
253 }
254
255
256 func (ident *DockerConfigEntry) UnmarshalJSON(data []byte) error {
257 var tmp dockerConfigEntryWithAuth
258 err := json.Unmarshal(data, &tmp)
259 if err != nil {
260 return err
261 }
262
263 ident.Username = tmp.Username
264 ident.Password = tmp.Password
265 ident.Email = tmp.Email
266
267 if len(tmp.Auth) == 0 {
268 return nil
269 }
270
271 ident.Username, ident.Password, err = decodeDockerConfigFieldAuth(tmp.Auth)
272 return err
273 }
274
275
276 func (ident DockerConfigEntry) MarshalJSON() ([]byte, error) {
277 toEncode := dockerConfigEntryWithAuth{ident.Username, ident.Password, ident.Email, ""}
278 toEncode.Auth = encodeDockerConfigFieldAuth(ident.Username, ident.Password)
279
280 return json.Marshal(toEncode)
281 }
282
283
284
285 func decodeDockerConfigFieldAuth(field string) (username, password string, err error) {
286
287 var decoded []byte
288
289
290
291 if strings.HasSuffix(strings.TrimSpace(field), "=") {
292
293 decoded, err = base64.StdEncoding.DecodeString(field)
294 } else {
295
296 decoded, err = base64.RawStdEncoding.DecodeString(field)
297 }
298
299 if err != nil {
300 return
301 }
302
303 parts := strings.SplitN(string(decoded), ":", 2)
304 if len(parts) != 2 {
305 err = fmt.Errorf("unable to parse auth field, must be formatted as base64(username:password)")
306 return
307 }
308
309 username = parts[0]
310 password = parts[1]
311
312 return
313 }
314
315 func encodeDockerConfigFieldAuth(username, password string) string {
316 fieldValue := username + ":" + password
317
318 return base64.StdEncoding.EncodeToString([]byte(fieldValue))
319 }
320
View as plain text