| ... | ... |
@@ -83,6 +83,7 @@ pages: |
| 83 | 83 |
- ['articles/security.md', 'Articles', 'Security'] |
| 84 | 84 |
- ['articles/https.md', 'Articles', 'Running Docker with HTTPS'] |
| 85 | 85 |
- ['articles/host_integration.md', 'Articles', 'Automatically starting Containers'] |
| 86 |
+- ['articles/certificates.md', 'Articles', 'Using certificates for repository client verification'] |
|
| 86 | 87 |
- ['articles/using_supervisord.md', 'Articles', 'Using Supervisor'] |
| 87 | 88 |
- ['articles/cfengine_process_management.md', 'Articles', 'Process management with CFEngine'] |
| 88 | 89 |
- ['articles/puppet.md', 'Articles', 'Using Puppet'] |
| 89 | 90 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,83 @@ |
| 0 |
+page_title: Using certificates for repository client verification |
|
| 1 |
+page_description: How to set up per-repository client certificates |
|
| 2 |
+page_keywords: Usage, repository, certificate, root, docker, documentation, examples |
|
| 3 |
+ |
|
| 4 |
+# Using certificates for repository client verification |
|
| 5 |
+ |
|
| 6 |
+This lets you specify custom client TLS certificates and CA root for a |
|
| 7 |
+specific registry hostname. Docker will then verify the registry |
|
| 8 |
+against the CA and present the client cert when talking to that |
|
| 9 |
+registry. This allows the registry to verify that the client has a |
|
| 10 |
+proper key, indicating that the client is allowed to access the |
|
| 11 |
+images. |
|
| 12 |
+ |
|
| 13 |
+A custom cert is configured by creating a directory in |
|
| 14 |
+`/etc/docker/certs.d` with the same name as the registry hostname. Inside |
|
| 15 |
+this directory all .crt files are added as CA Roots (if none exists, |
|
| 16 |
+the system default is used) and pair of files `$filename.key` and |
|
| 17 |
+`$filename.cert` indicate a custom certificate to present to the |
|
| 18 |
+registry. |
|
| 19 |
+ |
|
| 20 |
+If there are multiple certificates each one will be tried in |
|
| 21 |
+alphabetical order, proceeding to the next if we get a 403 of 5xx |
|
| 22 |
+response. |
|
| 23 |
+ |
|
| 24 |
+So, an example setup would be:: |
|
| 25 |
+ |
|
| 26 |
+ /etc/docker/certs.d/ |
|
| 27 |
+ └── localhost |
|
| 28 |
+ ├── client.cert |
|
| 29 |
+ ├── client.key |
|
| 30 |
+ └── localhost.crt |
|
| 31 |
+ |
|
| 32 |
+A simple way to test this setup is to use an apache server to host a |
|
| 33 |
+registry. Just copy a registry tree into the apache root, |
|
| 34 |
+[here](http://people.gnome.org/~alexl/v1.tar.gz) is an example one |
|
| 35 |
+containing the busybox image. |
|
| 36 |
+ |
|
| 37 |
+Then add this conf file as `/etc/httpd/conf.d/registry.conf`: |
|
| 38 |
+ |
|
| 39 |
+ # This must be in the root context, otherwise it causes a re-negotiation |
|
| 40 |
+ # which is not supported by the tls implementation in go |
|
| 41 |
+ SSLVerifyClient optional_no_ca |
|
| 42 |
+ |
|
| 43 |
+ <Location /v1> |
|
| 44 |
+ Action cert-protected /cgi-bin/cert.cgi |
|
| 45 |
+ SetHandler cert-protected |
|
| 46 |
+ |
|
| 47 |
+ Header set x-docker-registry-version "0.6.2" |
|
| 48 |
+ SetEnvIf Host (.*) custom_host=$1 |
|
| 49 |
+ Header set X-Docker-Endpoints "%{custom_host}e"
|
|
| 50 |
+ </Location> |
|
| 51 |
+ |
|
| 52 |
+And this as `/var/www/cgi-bin/cert.cgi`: |
|
| 53 |
+ |
|
| 54 |
+ #!/bin/bash |
|
| 55 |
+ if [ "$HTTPS" != "on" ]; then |
|
| 56 |
+ echo "Status: 403 Not using SSL" |
|
| 57 |
+ echo "x-docker-registry-version: 0.6.2" |
|
| 58 |
+ echo |
|
| 59 |
+ exit 0 |
|
| 60 |
+ fi |
|
| 61 |
+ if [ "$SSL_CLIENT_VERIFY" == "NONE" ]; then |
|
| 62 |
+ echo "Status: 403 Client certificate invalid" |
|
| 63 |
+ echo "x-docker-registry-version: 0.6.2" |
|
| 64 |
+ echo |
|
| 65 |
+ exit 0 |
|
| 66 |
+ fi |
|
| 67 |
+ echo "Content-length: $(stat --printf='%s' $PATH_TRANSLATED)" |
|
| 68 |
+ echo "x-docker-registry-version: 0.6.2" |
|
| 69 |
+ echo "X-Docker-Endpoints: $SERVER_NAME" |
|
| 70 |
+ echo "X-Docker-Size: 0" |
|
| 71 |
+ echo |
|
| 72 |
+ |
|
| 73 |
+ cat $PATH_TRANSLATED |
|
| 74 |
+ |
|
| 75 |
+This will return 403 for all accessed to `/v1` unless any client cert is |
|
| 76 |
+presented. Obviously a real implementation would verify more details |
|
| 77 |
+about the certificate. |
|
| 78 |
+ |
|
| 79 |
+Example client certs can be generated with:: |
|
| 80 |
+ |
|
| 81 |
+ openssl genrsa -out client.key 1024 |
|
| 82 |
+ openssl req -new -x509 -text -key client.key -out client.cert |
| 0 | 83 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+# Use |
|
| 1 |
+ |
|
| 2 |
+## Contents: |
|
| 3 |
+ |
|
| 4 |
+ - [First steps with Docker](basics/) |
|
| 5 |
+ - [Share Images via Repositories](workingwithrepository/) |
|
| 6 |
+ - [Redirect Ports](port_redirection/) |
|
| 7 |
+ - [Configure Networking](networking/) |
|
| 8 |
+ - [Automatically Start Containers](host_integration/) |
|
| 9 |
+ - [Share Directories via Volumes](working_with_volumes/) |
|
| 10 |
+ - [Link Containers](working_with_links_names/) |
|
| 11 |
+ - [Link via an Ambassador Container](ambassador_pattern_linking/) |
|
| 12 |
+ - [Using Puppet](puppet/) |
|
| 13 |
+ - [Using certificates for repository client verification](certificates/) |
| ... | ... |
@@ -4,6 +4,8 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"crypto/sha256" |
| 6 | 6 |
_ "crypto/sha512" |
| 7 |
+ "crypto/tls" |
|
| 8 |
+ "crypto/x509" |
|
| 7 | 9 |
"encoding/json" |
| 8 | 10 |
"errors" |
| 9 | 11 |
"fmt" |
| ... | ... |
@@ -13,6 +15,8 @@ import ( |
| 13 | 13 |
"net/http" |
| 14 | 14 |
"net/http/cookiejar" |
| 15 | 15 |
"net/url" |
| 16 |
+ "os" |
|
| 17 |
+ "path" |
|
| 16 | 18 |
"regexp" |
| 17 | 19 |
"runtime" |
| 18 | 20 |
"strconv" |
| ... | ... |
@@ -29,31 +33,155 @@ var ( |
| 29 | 29 |
errLoginRequired = errors.New("Authentication is required.")
|
| 30 | 30 |
) |
| 31 | 31 |
|
| 32 |
+type TimeoutType uint32 |
|
| 33 |
+ |
|
| 34 |
+const ( |
|
| 35 |
+ NoTimeout TimeoutType = iota |
|
| 36 |
+ ReceiveTimeout |
|
| 37 |
+ ConnectTimeout |
|
| 38 |
+) |
|
| 39 |
+ |
|
| 40 |
+func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType) *http.Client {
|
|
| 41 |
+ tlsConfig := tls.Config{RootCAs: roots}
|
|
| 42 |
+ |
|
| 43 |
+ if cert != nil {
|
|
| 44 |
+ tlsConfig.Certificates = append(tlsConfig.Certificates, *cert) |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ httpTransport := &http.Transport{
|
|
| 48 |
+ DisableKeepAlives: true, |
|
| 49 |
+ Proxy: http.ProxyFromEnvironment, |
|
| 50 |
+ TLSClientConfig: &tlsConfig, |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ switch timeout {
|
|
| 54 |
+ case ConnectTimeout: |
|
| 55 |
+ httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
| 56 |
+ // Set the connect timeout to 5 seconds |
|
| 57 |
+ conn, err := net.DialTimeout(proto, addr, 5*time.Second) |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return nil, err |
|
| 60 |
+ } |
|
| 61 |
+ // Set the recv timeout to 10 seconds |
|
| 62 |
+ conn.SetDeadline(time.Now().Add(10 * time.Second)) |
|
| 63 |
+ return conn, nil |
|
| 64 |
+ } |
|
| 65 |
+ case ReceiveTimeout: |
|
| 66 |
+ httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
| 67 |
+ conn, err := net.Dial(proto, addr) |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ return nil, err |
|
| 70 |
+ } |
|
| 71 |
+ conn = utils.NewTimeoutConn(conn, 1*time.Minute) |
|
| 72 |
+ return conn, nil |
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ return &http.Client{
|
|
| 77 |
+ Transport: httpTransport, |
|
| 78 |
+ CheckRedirect: AddRequiredHeadersToRedirectedRequests, |
|
| 79 |
+ Jar: jar, |
|
| 80 |
+ } |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*http.Response, *http.Client, error) {
|
|
| 84 |
+ hasFile := func(files []os.FileInfo, name string) bool {
|
|
| 85 |
+ for _, f := range files {
|
|
| 86 |
+ if f.Name() == name {
|
|
| 87 |
+ return true |
|
| 88 |
+ } |
|
| 89 |
+ } |
|
| 90 |
+ return false |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
|
|
| 94 |
+ fs, err := ioutil.ReadDir(hostDir) |
|
| 95 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 96 |
+ return nil, nil, err |
|
| 97 |
+ } |
|
| 98 |
+ |
|
| 99 |
+ var ( |
|
| 100 |
+ pool *x509.CertPool |
|
| 101 |
+ certs []*tls.Certificate |
|
| 102 |
+ ) |
|
| 103 |
+ |
|
| 104 |
+ for _, f := range fs {
|
|
| 105 |
+ if strings.HasSuffix(f.Name(), ".crt") {
|
|
| 106 |
+ if pool == nil {
|
|
| 107 |
+ pool = x509.NewCertPool() |
|
| 108 |
+ } |
|
| 109 |
+ data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) |
|
| 110 |
+ if err != nil {
|
|
| 111 |
+ return nil, nil, err |
|
| 112 |
+ } else {
|
|
| 113 |
+ pool.AppendCertsFromPEM(data) |
|
| 114 |
+ } |
|
| 115 |
+ } |
|
| 116 |
+ if strings.HasSuffix(f.Name(), ".cert") {
|
|
| 117 |
+ certName := f.Name() |
|
| 118 |
+ keyName := certName[:len(certName)-5] + ".key" |
|
| 119 |
+ if !hasFile(fs, keyName) {
|
|
| 120 |
+ return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
|
|
| 121 |
+ } else {
|
|
| 122 |
+ cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName)) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return nil, nil, err |
|
| 125 |
+ } |
|
| 126 |
+ certs = append(certs, &cert) |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 129 |
+ if strings.HasSuffix(f.Name(), ".key") {
|
|
| 130 |
+ keyName := f.Name() |
|
| 131 |
+ certName := keyName[:len(keyName)-4] + ".cert" |
|
| 132 |
+ if !hasFile(fs, certName) {
|
|
| 133 |
+ return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
|
|
| 134 |
+ } |
|
| 135 |
+ } |
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 138 |
+ if len(certs) == 0 {
|
|
| 139 |
+ client := newClient(jar, pool, nil, timeout) |
|
| 140 |
+ res, err := client.Do(req) |
|
| 141 |
+ if err != nil {
|
|
| 142 |
+ return nil, nil, err |
|
| 143 |
+ } |
|
| 144 |
+ return res, client, nil |
|
| 145 |
+ } else {
|
|
| 146 |
+ for i, cert := range certs {
|
|
| 147 |
+ client := newClient(jar, pool, cert, timeout) |
|
| 148 |
+ res, err := client.Do(req) |
|
| 149 |
+ if i == len(certs)-1 {
|
|
| 150 |
+ // If this is the last cert, always return the result |
|
| 151 |
+ return res, client, err |
|
| 152 |
+ } else {
|
|
| 153 |
+ // Otherwise, continue to next cert if 403 or 5xx |
|
| 154 |
+ if err == nil && res.StatusCode != 403 && !(res.StatusCode >= 500 && res.StatusCode < 600) {
|
|
| 155 |
+ return res, client, err |
|
| 156 |
+ } |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ return nil, nil, nil |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 32 | 164 |
func pingRegistryEndpoint(endpoint string) (RegistryInfo, error) {
|
| 33 | 165 |
if endpoint == IndexServerAddress() {
|
| 34 | 166 |
// Skip the check, we now this one is valid |
| 35 | 167 |
// (and we never want to fallback to http in case of error) |
| 36 | 168 |
return RegistryInfo{Standalone: false}, nil
|
| 37 | 169 |
} |
| 38 |
- httpDial := func(proto string, addr string) (net.Conn, error) {
|
|
| 39 |
- // Set the connect timeout to 5 seconds |
|
| 40 |
- conn, err := net.DialTimeout(proto, addr, 5*time.Second) |
|
| 41 |
- if err != nil {
|
|
| 42 |
- return nil, err |
|
| 43 |
- } |
|
| 44 |
- // Set the recv timeout to 10 seconds |
|
| 45 |
- conn.SetDeadline(time.Now().Add(10 * time.Second)) |
|
| 46 |
- return conn, nil |
|
| 47 |
- } |
|
| 48 |
- httpTransport := &http.Transport{
|
|
| 49 |
- Dial: httpDial, |
|
| 50 |
- Proxy: http.ProxyFromEnvironment, |
|
| 170 |
+ |
|
| 171 |
+ req, err := http.NewRequest("GET", endpoint+"_ping", nil)
|
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ return RegistryInfo{Standalone: false}, err
|
|
| 51 | 174 |
} |
| 52 |
- client := &http.Client{Transport: httpTransport}
|
|
| 53 |
- resp, err := client.Get(endpoint + "_ping") |
|
| 175 |
+ |
|
| 176 |
+ resp, _, err := doRequest(req, nil, ConnectTimeout) |
|
| 54 | 177 |
if err != nil {
|
| 55 | 178 |
return RegistryInfo{Standalone: false}, err
|
| 56 | 179 |
} |
| 180 |
+ |
|
| 57 | 181 |
defer resp.Body.Close() |
| 58 | 182 |
|
| 59 | 183 |
jsonString, err := ioutil.ReadAll(resp.Body) |
| ... | ... |
@@ -171,6 +299,10 @@ func setTokenAuth(req *http.Request, token []string) {
|
| 171 | 171 |
} |
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 |
+func (r *Registry) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
|
|
| 175 |
+ return doRequest(req, r.jar, r.timeout) |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 174 | 178 |
// Retrieve the history of a given image from the Registry. |
| 175 | 179 |
// Return a list of the parent's json (requested image included) |
| 176 | 180 |
func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
|
| ... | ... |
@@ -179,7 +311,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s |
| 179 | 179 |
return nil, err |
| 180 | 180 |
} |
| 181 | 181 |
setTokenAuth(req, token) |
| 182 |
- res, err := r.client.Do(req) |
|
| 182 |
+ res, _, err := r.doRequest(req) |
|
| 183 | 183 |
if err != nil {
|
| 184 | 184 |
return nil, err |
| 185 | 185 |
} |
| ... | ... |
@@ -214,7 +346,7 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo |
| 214 | 214 |
return false |
| 215 | 215 |
} |
| 216 | 216 |
setTokenAuth(req, token) |
| 217 |
- res, err := r.client.Do(req) |
|
| 217 |
+ res, _, err := r.doRequest(req) |
|
| 218 | 218 |
if err != nil {
|
| 219 | 219 |
utils.Errorf("Error in LookupRemoteImage %s", err)
|
| 220 | 220 |
return false |
| ... | ... |
@@ -231,7 +363,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ |
| 231 | 231 |
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
| 232 | 232 |
} |
| 233 | 233 |
setTokenAuth(req, token) |
| 234 |
- res, err := r.client.Do(req) |
|
| 234 |
+ res, _, err := r.doRequest(req) |
|
| 235 | 235 |
if err != nil {
|
| 236 | 236 |
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
| 237 | 237 |
} |
| ... | ... |
@@ -260,6 +392,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, i |
| 260 | 260 |
var ( |
| 261 | 261 |
retries = 5 |
| 262 | 262 |
headRes *http.Response |
| 263 |
+ client *http.Client |
|
| 263 | 264 |
hasResume bool = false |
| 264 | 265 |
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
|
| 265 | 266 |
) |
| ... | ... |
@@ -267,9 +400,10 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, i |
| 267 | 267 |
if err != nil {
|
| 268 | 268 |
return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
|
| 269 | 269 |
} |
| 270 |
+ |
|
| 270 | 271 |
setTokenAuth(headReq, token) |
| 271 | 272 |
for i := 1; i <= retries; i++ {
|
| 272 |
- headRes, err = r.client.Do(headReq) |
|
| 273 |
+ headRes, client, err = r.doRequest(headReq) |
|
| 273 | 274 |
if err != nil && i == retries {
|
| 274 | 275 |
return nil, fmt.Errorf("Eror while making head request: %s\n", err)
|
| 275 | 276 |
} else if err != nil {
|
| ... | ... |
@@ -290,10 +424,10 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, i |
| 290 | 290 |
setTokenAuth(req, token) |
| 291 | 291 |
if hasResume {
|
| 292 | 292 |
utils.Debugf("server supports resume")
|
| 293 |
- return utils.ResumableRequestReader(r.client, req, 5, imgSize), nil |
|
| 293 |
+ return utils.ResumableRequestReader(client, req, 5, imgSize), nil |
|
| 294 | 294 |
} |
| 295 | 295 |
utils.Debugf("server doesn't support resume")
|
| 296 |
- res, err := r.client.Do(req) |
|
| 296 |
+ res, _, err := r.doRequest(req) |
|
| 297 | 297 |
if err != nil {
|
| 298 | 298 |
return nil, err |
| 299 | 299 |
} |
| ... | ... |
@@ -319,7 +453,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ |
| 319 | 319 |
return nil, err |
| 320 | 320 |
} |
| 321 | 321 |
setTokenAuth(req, token) |
| 322 |
- res, err := r.client.Do(req) |
|
| 322 |
+ res, _, err := r.doRequest(req) |
|
| 323 | 323 |
if err != nil {
|
| 324 | 324 |
return nil, err |
| 325 | 325 |
} |
| ... | ... |
@@ -380,7 +514,7 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
|
| 380 | 380 |
} |
| 381 | 381 |
req.Header.Set("X-Docker-Token", "true")
|
| 382 | 382 |
|
| 383 |
- res, err := r.client.Do(req) |
|
| 383 |
+ res, _, err := r.doRequest(req) |
|
| 384 | 384 |
if err != nil {
|
| 385 | 385 |
return nil, err |
| 386 | 386 |
} |
| ... | ... |
@@ -448,13 +582,13 @@ func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, |
| 448 | 448 |
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
| 449 | 449 |
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
|
| 450 | 450 |
|
| 451 |
- res, err := r.client.Do(req) |
|
| 451 |
+ res, _, err := r.doRequest(req) |
|
| 452 | 452 |
if err != nil {
|
| 453 | 453 |
return fmt.Errorf("Failed to upload metadata: %s", err)
|
| 454 | 454 |
} |
| 455 | 455 |
defer res.Body.Close() |
| 456 | 456 |
if len(res.Cookies()) > 0 {
|
| 457 |
- r.client.Jar.SetCookies(req.URL, res.Cookies()) |
|
| 457 |
+ r.jar.SetCookies(req.URL, res.Cookies()) |
|
| 458 | 458 |
} |
| 459 | 459 |
if res.StatusCode != 200 {
|
| 460 | 460 |
errBody, err := ioutil.ReadAll(res.Body) |
| ... | ... |
@@ -484,7 +618,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis |
| 484 | 484 |
req.Header.Add("Content-type", "application/json")
|
| 485 | 485 |
setTokenAuth(req, token) |
| 486 | 486 |
|
| 487 |
- res, err := r.client.Do(req) |
|
| 487 |
+ res, _, err := r.doRequest(req) |
|
| 488 | 488 |
if err != nil {
|
| 489 | 489 |
return fmt.Errorf("Failed to upload metadata: %s", err)
|
| 490 | 490 |
} |
| ... | ... |
@@ -525,7 +659,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr |
| 525 | 525 |
req.ContentLength = -1 |
| 526 | 526 |
req.TransferEncoding = []string{"chunked"}
|
| 527 | 527 |
setTokenAuth(req, token) |
| 528 |
- res, err := r.client.Do(req) |
|
| 528 |
+ res, _, err := r.doRequest(req) |
|
| 529 | 529 |
if err != nil {
|
| 530 | 530 |
return "", "", fmt.Errorf("Failed to upload layer: %s", err)
|
| 531 | 531 |
} |
| ... | ... |
@@ -562,7 +696,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token |
| 562 | 562 |
req.Header.Add("Content-type", "application/json")
|
| 563 | 563 |
setTokenAuth(req, token) |
| 564 | 564 |
req.ContentLength = int64(len(revision)) |
| 565 |
- res, err := r.client.Do(req) |
|
| 565 |
+ res, _, err := r.doRequest(req) |
|
| 566 | 566 |
if err != nil {
|
| 567 | 567 |
return err |
| 568 | 568 |
} |
| ... | ... |
@@ -610,7 +744,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat |
| 610 | 610 |
req.Header["X-Docker-Endpoints"] = regs |
| 611 | 611 |
} |
| 612 | 612 |
|
| 613 |
- res, err := r.client.Do(req) |
|
| 613 |
+ res, _, err := r.doRequest(req) |
|
| 614 | 614 |
if err != nil {
|
| 615 | 615 |
return nil, err |
| 616 | 616 |
} |
| ... | ... |
@@ -629,7 +763,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat |
| 629 | 629 |
if validate {
|
| 630 | 630 |
req.Header["X-Docker-Endpoints"] = regs |
| 631 | 631 |
} |
| 632 |
- res, err = r.client.Do(req) |
|
| 632 |
+ res, _, err := r.doRequest(req) |
|
| 633 | 633 |
if err != nil {
|
| 634 | 634 |
return nil, err |
| 635 | 635 |
} |
| ... | ... |
@@ -688,7 +822,7 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
|
| 688 | 688 |
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) |
| 689 | 689 |
} |
| 690 | 690 |
req.Header.Set("X-Docker-Token", "true")
|
| 691 |
- res, err := r.client.Do(req) |
|
| 691 |
+ res, _, err := r.doRequest(req) |
|
| 692 | 692 |
if err != nil {
|
| 693 | 693 |
return nil, err |
| 694 | 694 |
} |
| ... | ... |
@@ -750,10 +884,11 @@ type RegistryInfo struct {
|
| 750 | 750 |
} |
| 751 | 751 |
|
| 752 | 752 |
type Registry struct {
|
| 753 |
- client *http.Client |
|
| 754 | 753 |
authConfig *AuthConfig |
| 755 | 754 |
reqFactory *utils.HTTPRequestFactory |
| 756 | 755 |
indexEndpoint string |
| 756 |
+ jar *cookiejar.Jar |
|
| 757 |
+ timeout TimeoutType |
|
| 757 | 758 |
} |
| 758 | 759 |
|
| 759 | 760 |
func trustedLocation(req *http.Request) bool {
|
| ... | ... |
@@ -791,30 +926,16 @@ func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque |
| 791 | 791 |
} |
| 792 | 792 |
|
| 793 | 793 |
func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string, timeout bool) (r *Registry, err error) {
|
| 794 |
- httpTransport := &http.Transport{
|
|
| 795 |
- DisableKeepAlives: true, |
|
| 796 |
- Proxy: http.ProxyFromEnvironment, |
|
| 797 |
- } |
|
| 798 |
- if timeout {
|
|
| 799 |
- httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
| 800 |
- conn, err := net.Dial(proto, addr) |
|
| 801 |
- if err != nil {
|
|
| 802 |
- return nil, err |
|
| 803 |
- } |
|
| 804 |
- conn = utils.NewTimeoutConn(conn, 1*time.Minute) |
|
| 805 |
- return conn, nil |
|
| 806 |
- } |
|
| 807 |
- } |
|
| 808 | 794 |
r = &Registry{
|
| 809 |
- authConfig: authConfig, |
|
| 810 |
- client: &http.Client{
|
|
| 811 |
- Transport: httpTransport, |
|
| 812 |
- CheckRedirect: AddRequiredHeadersToRedirectedRequests, |
|
| 813 |
- }, |
|
| 795 |
+ authConfig: authConfig, |
|
| 814 | 796 |
indexEndpoint: indexEndpoint, |
| 815 | 797 |
} |
| 816 | 798 |
|
| 817 |
- r.client.Jar, err = cookiejar.New(nil) |
|
| 799 |
+ if timeout {
|
|
| 800 |
+ r.timeout = ReceiveTimeout |
|
| 801 |
+ } |
|
| 802 |
+ |
|
| 803 |
+ r.jar, err = cookiejar.New(nil) |
|
| 818 | 804 |
if err != nil {
|
| 819 | 805 |
return nil, err |
| 820 | 806 |
} |