Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@imjasonh
Created March 5, 2018 19:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save imjasonh/7ef2fa187338fff54711038b3f1a0350 to your computer and use it in GitHub Desktop.
Save imjasonh/7ef2fa187338fff54711038b3f1a0350 to your computer and use it in GitHub Desktop.
Docker registry API client transport that invokes configured creds helpers
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