Created
March 5, 2018 19:36
-
-
Save imjasonh/7ef2fa187338fff54711038b3f1a0350 to your computer and use it in GitHub Desktop.
Docker registry API client transport that invokes configured creds helpers
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
package registry | |
import ( | |
"bytes" | |
"encoding/base64" | |
"encoding/json" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"os" | |
"os/exec" | |
"path" | |
"strings" | |
) | |
func DockerCredsTransport(inner http.RoundTripper) http.RoundTripper { | |
if inner == nil { | |
inner = http.DefaultTransport | |
} | |
return transport{inner} | |
} | |
type transport struct { | |
inner http.RoundTripper | |
} | |
func (t transport) RoundTrip(r *http.Request) (*http.Response, error) { | |
if r.Header.Get("Authorization") != "" { | |
return t.inner.RoundTrip(r) | |
} | |
dir := os.Getenv("HOME") | |
if override := os.Getenv("DOCKER_CONFIG"); override != "" { | |
dir = override | |
} | |
f, err := os.Open(path.Join(dir, ".docker/config.json")) | |
if err != nil { | |
return nil, fmt.Errorf("error opening ~/.docker/config.json: %v", err) | |
} | |
defer f.Close() | |
var config struct { | |
CredHelpers map[string]string `json:"credHelpers"` | |
CredsStore struct{} `json:"credsStore"` | |
Auths map[string]struct { | |
Auth string `json:"auth"` | |
} `json:"auths"` | |
} | |
if err := json.NewDecoder(f).Decode(&config); err != nil { | |
return nil, fmt.Errorf("error decoding ~/.docker/config.json: %v", err) | |
} | |
formats := []string{ | |
// naked domain | |
"%s", | |
// scheme-prefixed | |
"http://%s", | |
"https://%s", | |
// scheme-prefixed with version in URL path | |
"http://%s/v1/", | |
"https://%s/v1/", | |
"http://%s/v2/", | |
"https://%s/v2/", | |
} | |
// Look for a matching credential helper and invoke it. | |
for host, helper := range config.CredHelpers { | |
for _, f := range formats { | |
if r.URL.Host == fmt.Sprintf(f, host) { | |
auth, err := invokeHelper(helper, fmt.Sprintf("https://%s", host)) | |
if err != nil { | |
return nil, fmt.Errorf("error invoking credential helper for %q: %v", helper, err) | |
} | |
r.Header.Set("Authorization", "Basic "+auth) | |
return t.inner.RoundTrip(r) | |
} | |
} | |
} | |
// TODO: Support credsStore. | |
// Look for auths (base64-encoded username:password) and use it directly. | |
for host, auth := range config.Auths { | |
for _, f := range formats { | |
if r.URL.Host == fmt.Sprintf(f, host) { | |
r.Header.Set("Authorization", "Basic "+auth.Auth) | |
return t.inner.RoundTrip(r) | |
} | |
} | |
} | |
// Fallback to sending request without creds. | |
return t.inner.RoundTrip(r) | |
} | |
func invokeHelper(helper, serverURL string) (string, error) { | |
cmd := exec.Command(fmt.Sprintf("docker-credential-%s", helper), "get") | |
cmd.Stdin = strings.NewReader(serverURL) | |
var buf bytes.Buffer | |
cmd.Stdout = &buf | |
if err := cmd.Run(); err != nil { | |
return "", fmt.Errorf("error executing helper: %v", err) | |
} | |
var output struct { | |
Username string `json:"username"` | |
Secret string `json:"secret"` | |
} | |
if err := json.NewDecoder(&buf).Decode(&output); err != nil { | |
return "", fmt.Errorf("error decoding output: %v", err) | |
} | |
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", output.Username, output.Secret))), nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment