2013-05-15 03:41:39 +02:00
|
|
|
package registry
|
|
|
|
|
|
|
|
import (
|
2013-12-04 15:03:51 +01:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2013-05-15 22:22:57 +02:00
|
|
|
"errors"
|
2013-05-15 03:41:39 +02:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2013-08-05 02:42:24 +02:00
|
|
|
"net"
|
2013-05-15 03:41:39 +02:00
|
|
|
"net/http"
|
2015-05-14 16:12:54 +02:00
|
|
|
"net/http/httputil"
|
2013-12-04 15:03:51 +01:00
|
|
|
"os"
|
|
|
|
"path"
|
2015-05-14 16:12:54 +02:00
|
|
|
"runtime"
|
2013-05-15 03:41:39 +02:00
|
|
|
"strings"
|
2013-08-05 02:42:24 +02:00
|
|
|
"time"
|
2014-04-29 11:01:07 +02:00
|
|
|
|
2015-03-26 23:22:04 +01:00
|
|
|
"github.com/Sirupsen/logrus"
|
2015-05-14 16:12:54 +02:00
|
|
|
"github.com/docker/docker/autogen/dockerversion"
|
|
|
|
"github.com/docker/docker/pkg/parsers/kernel"
|
2015-02-25 20:52:37 +01:00
|
|
|
"github.com/docker/docker/pkg/timeoutconn"
|
2015-05-16 00:03:08 +02:00
|
|
|
"github.com/docker/docker/pkg/useragent"
|
2013-05-15 03:41:39 +02:00
|
|
|
)
|
|
|
|
|
2013-07-22 23:50:32 +02:00
|
|
|
var (
|
2015-01-08 00:42:01 +01:00
|
|
|
ErrAlreadyExists = errors.New("Image already exists")
|
|
|
|
ErrDoesNotExist = errors.New("Image does not exist")
|
|
|
|
errLoginRequired = errors.New("Authentication is required.")
|
2013-07-22 23:50:32 +02:00
|
|
|
)
|
2013-05-15 22:22:57 +02:00
|
|
|
|
2013-12-04 15:03:51 +01:00
|
|
|
type TimeoutType uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
NoTimeout TimeoutType = iota
|
|
|
|
ReceiveTimeout
|
|
|
|
ConnectTimeout
|
|
|
|
)
|
|
|
|
|
2015-05-14 16:12:54 +02:00
|
|
|
type httpsTransport struct {
|
|
|
|
*http.Transport
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
|
|
|
|
2015-05-14 16:12:54 +02:00
|
|
|
// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
|
|
|
|
// it's because it's so as to match the current behavior in master: we generate the
|
|
|
|
// certpool on every-goddam-request. It's not great, but it allows people to just put
|
|
|
|
// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
|
|
|
|
// prefer an fsnotify implementation, but that was out of scope of my refactoring.
|
|
|
|
// TODO: improve things
|
|
|
|
func (tr *httpsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
2013-12-04 15:03:51 +01:00
|
|
|
var (
|
2015-05-14 16:12:54 +02:00
|
|
|
roots *x509.CertPool
|
2014-10-09 19:52:30 +02:00
|
|
|
certs []tls.Certificate
|
2013-12-04 15:03:51 +01:00
|
|
|
)
|
|
|
|
|
2015-05-14 16:12:54 +02:00
|
|
|
if req.URL.Scheme == "https" {
|
2014-10-11 05:22:12 +02:00
|
|
|
hasFile := func(files []os.FileInfo, name string) bool {
|
|
|
|
for _, f := range files {
|
|
|
|
if f.Name() == name {
|
|
|
|
return true
|
|
|
|
}
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
2014-10-11 05:22:12 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
|
2015-03-26 23:22:04 +01:00
|
|
|
logrus.Debugf("hostDir: %s", hostDir)
|
2014-10-11 05:22:12 +02:00
|
|
|
fs, err := ioutil.ReadDir(hostDir)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2015-05-14 16:12:54 +02:00
|
|
|
return nil, err
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
2014-10-11 05:22:12 +02:00
|
|
|
|
|
|
|
for _, f := range fs {
|
|
|
|
if strings.HasSuffix(f.Name(), ".crt") {
|
2015-05-14 16:12:54 +02:00
|
|
|
if roots == nil {
|
|
|
|
roots = x509.NewCertPool()
|
2014-10-11 05:22:12 +02:00
|
|
|
}
|
2015-03-26 23:22:04 +01:00
|
|
|
logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
|
2014-10-11 05:22:12 +02:00
|
|
|
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
|
|
|
|
if err != nil {
|
2015-05-14 16:12:54 +02:00
|
|
|
return nil, err
|
2014-10-11 05:22:12 +02:00
|
|
|
}
|
2015-05-14 16:12:54 +02:00
|
|
|
roots.AppendCertsFromPEM(data)
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
2014-10-11 05:22:12 +02:00
|
|
|
if strings.HasSuffix(f.Name(), ".cert") {
|
|
|
|
certName := f.Name()
|
|
|
|
keyName := certName[:len(certName)-5] + ".key"
|
2015-03-26 23:22:04 +01:00
|
|
|
logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
|
2014-10-11 05:22:12 +02:00
|
|
|
if !hasFile(fs, keyName) {
|
2015-05-14 16:12:54 +02:00
|
|
|
return nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
|
2014-10-11 05:22:12 +02:00
|
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
|
|
|
|
if err != nil {
|
2015-05-14 16:12:54 +02:00
|
|
|
return nil, err
|
2014-10-11 05:22:12 +02:00
|
|
|
}
|
2014-10-09 19:52:30 +02:00
|
|
|
certs = append(certs, cert)
|
2014-08-25 18:50:18 +02:00
|
|
|
}
|
2014-10-11 05:22:12 +02:00
|
|
|
if strings.HasSuffix(f.Name(), ".key") {
|
|
|
|
keyName := f.Name()
|
|
|
|
certName := keyName[:len(keyName)-4] + ".cert"
|
2015-03-26 23:22:04 +01:00
|
|
|
logrus.Debugf("key: %s", hostDir+"/"+f.Name())
|
2014-10-11 05:22:12 +02:00
|
|
|
if !hasFile(fs, certName) {
|
2015-05-14 16:12:54 +02:00
|
|
|
return nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
|
2014-10-11 05:22:12 +02:00
|
|
|
}
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
|
|
|
}
|
2015-05-14 16:12:54 +02:00
|
|
|
if tr.Transport.TLSClientConfig == nil {
|
|
|
|
tr.Transport.TLSClientConfig = &tls.Config{
|
|
|
|
// Avoid fallback to SSL protocols < TLS1.0
|
|
|
|
MinVersion: tls.VersionTLS10,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tr.Transport.TLSClientConfig.RootCAs = roots
|
|
|
|
tr.Transport.TLSClientConfig.Certificates = certs
|
|
|
|
}
|
|
|
|
return tr.Transport.RoundTrip(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
|
|
|
|
tlsConfig := tls.Config{
|
|
|
|
// Avoid fallback to SSL protocols < TLS1.0
|
|
|
|
MinVersion: tls.VersionTLS10,
|
|
|
|
InsecureSkipVerify: !secure,
|
|
|
|
}
|
|
|
|
|
|
|
|
transport := &http.Transport{
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
TLSClientConfig: &tlsConfig,
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
|
|
|
|
2015-05-14 16:12:54 +02:00
|
|
|
switch timeout {
|
|
|
|
case ConnectTimeout:
|
|
|
|
transport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
|
|
// Set the connect timeout to 30 seconds to allow for slower connection
|
|
|
|
// times...
|
|
|
|
d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
|
|
|
|
|
|
|
|
conn, err := d.Dial(proto, addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Set the recv timeout to 10 seconds
|
|
|
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
case ReceiveTimeout:
|
|
|
|
transport.Dial = func(proto string, addr string) (net.Conn, error) {
|
|
|
|
d := net.Dialer{DualStack: true}
|
|
|
|
|
|
|
|
conn, err := d.Dial(proto, addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conn = timeoutconn.New(conn, 1*time.Minute)
|
|
|
|
return conn, nil
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
2014-08-25 18:50:18 +02:00
|
|
|
}
|
2014-10-11 05:22:12 +02:00
|
|
|
|
2015-05-14 16:12:54 +02:00
|
|
|
if secure {
|
|
|
|
// note: httpsTransport also handles http transport
|
|
|
|
// but for HTTPS, it sets up the certs
|
|
|
|
return &httpsTransport{transport}
|
|
|
|
}
|
|
|
|
|
|
|
|
return transport
|
|
|
|
}
|
|
|
|
|
|
|
|
type DockerHeaders struct {
|
|
|
|
http.RoundTripper
|
|
|
|
Headers http.Header
|
|
|
|
}
|
|
|
|
|
|
|
|
// cloneRequest returns a clone of the provided *http.Request.
|
|
|
|
// The clone is a shallow copy of the struct and its Header map
|
|
|
|
func cloneRequest(r *http.Request) *http.Request {
|
|
|
|
// shallow copy of the struct
|
|
|
|
r2 := new(http.Request)
|
|
|
|
*r2 = *r
|
|
|
|
// deep copy of the Header
|
|
|
|
r2.Header = make(http.Header, len(r.Header))
|
|
|
|
for k, s := range r.Header {
|
|
|
|
r2.Header[k] = append([]string(nil), s...)
|
|
|
|
}
|
|
|
|
return r2
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tr *DockerHeaders) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
req = cloneRequest(req)
|
2015-05-16 00:03:08 +02:00
|
|
|
httpVersion := make([]useragent.VersionInfo, 0, 4)
|
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
|
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
|
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
|
2015-05-14 16:12:54 +02:00
|
|
|
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
2015-05-16 00:03:08 +02:00
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
|
2015-05-14 16:12:54 +02:00
|
|
|
}
|
2015-05-16 00:03:08 +02:00
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
|
|
|
|
httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
|
2015-05-14 16:12:54 +02:00
|
|
|
|
2015-05-16 00:03:08 +02:00
|
|
|
userAgent := useragent.AppendVersions(req.UserAgent(), httpVersion...)
|
2015-05-14 16:12:54 +02:00
|
|
|
|
|
|
|
req.Header.Set("User-Agent", userAgent)
|
|
|
|
|
|
|
|
for k, v := range tr.Headers {
|
|
|
|
req.Header[k] = v
|
|
|
|
}
|
|
|
|
return tr.RoundTripper.RoundTrip(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
type debugTransport struct{ http.RoundTripper }
|
|
|
|
|
|
|
|
func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
|
|
dump, err := httputil.DumpRequestOut(req, false)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not dump request")
|
|
|
|
}
|
|
|
|
fmt.Println(string(dump))
|
|
|
|
resp, err := tr.RoundTripper.RoundTrip(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dump, err = httputil.DumpResponse(resp, false)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not dump response")
|
|
|
|
}
|
|
|
|
fmt.Println(string(dump))
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func HTTPClient(transport http.RoundTripper) *http.Client {
|
|
|
|
if transport == nil {
|
|
|
|
transport = NewTransport(ConnectTimeout, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &http.Client{
|
|
|
|
Transport: transport,
|
|
|
|
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
|
|
}
|
2013-12-04 15:03:51 +01:00
|
|
|
}
|
|
|
|
|
2014-06-05 20:37:37 +02:00
|
|
|
func trustedLocation(req *http.Request) bool {
|
|
|
|
var (
|
|
|
|
trusteds = []string{"docker.com", "docker.io"}
|
|
|
|
hostname = strings.SplitN(req.Host, ":", 2)[0]
|
|
|
|
)
|
|
|
|
if req.URL.Scheme != "https" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, trusted := range trusteds {
|
2014-06-07 23:17:56 +02:00
|
|
|
if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
|
2014-06-05 20:37:37 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-03-26 01:33:17 +01:00
|
|
|
func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
|
|
|
|
if via != nil && via[0] != nil {
|
2014-06-05 20:37:37 +02:00
|
|
|
if trustedLocation(req) && trustedLocation(via[0]) {
|
|
|
|
req.Header = via[0].Header
|
2014-08-25 18:50:18 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for k, v := range via[0].Header {
|
|
|
|
if k != "Authorization" {
|
|
|
|
for _, vv := range v {
|
|
|
|
req.Header.Add(k, vv)
|
2014-06-05 20:37:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-03-26 01:33:17 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|