4a92b8a0 |
// Package registry contains client primitives to interact with a remote Docker registry. |
9bb3dc98 |
package registry
import ( |
05243104 |
"crypto/tls" |
dc9d6c1c |
"errors" |
ed13c3ab |
"fmt"
"io/ioutil" |
c860945b |
"net" |
9bb3dc98 |
"net/http" |
05243104 |
"os" |
ed13c3ab |
"path/filepath" |
9bb3dc98 |
"strings" |
c860945b |
"time" |
44d54ba0 |
|
276c640b |
"github.com/docker/distribution/registry/client/transport" |
20702708 |
"github.com/docker/go-connections/sockets" |
8e034802 |
"github.com/docker/go-connections/tlsconfig" |
1009e6a4 |
"github.com/sirupsen/logrus" |
9bb3dc98 |
)
|
e3f68b22 |
var ( |
4fcb9ac4 |
// ErrAlreadyExists is an error returned if an image being pushed
// already exists on the remote side |
6f0068f2 |
ErrAlreadyExists = errors.New("Image already exists") |
e3f68b22 |
) |
dc9d6c1c |
|
e863a07b |
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
// PreferredServerCipherSuites should have no effect |
7a8c7b47 |
tlsConfig := tlsconfig.ServerDefault() |
e863a07b |
tlsConfig.InsecureSkipVerify = !isSecure
|
bcd0f0cd |
if isSecure && CertsDir != "" { |
831b0030 |
hostDir := filepath.Join(CertsDir, cleanPath(hostname)) |
e863a07b |
logrus.Debugf("hostDir: %s", hostDir) |
7a8c7b47 |
if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil { |
e863a07b |
return nil, err
}
}
|
7a8c7b47 |
return tlsConfig, nil |
e863a07b |
}
|
19515a7a |
func hasFile(files []os.FileInfo, name string) bool {
for _, f := range files {
if f.Name() == name {
return true |
05243104 |
} |
45a29238 |
} |
19515a7a |
return false |
a01cc3ca |
}
|
ed13c3ab |
// ReadCertsDirectory reads the directory for TLS certificates
// including roots and certificate pairs and updates the
// provided TLS configuration.
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
fs, err := ioutil.ReadDir(directory)
if err != nil && !os.IsNotExist(err) {
return err
}
for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") {
if tlsConfig.RootCAs == nil { |
66a5e34c |
systemPool, err := tlsconfig.SystemCertPool()
if err != nil {
return fmt.Errorf("unable to get system cert pool: %v", err)
}
tlsConfig.RootCAs = systemPool |
ed13c3ab |
}
logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
if err != nil {
return err
}
tlsConfig.RootCAs.AppendCertsFromPEM(data)
}
if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
if !hasFile(fs, keyName) { |
9b47b7b1 |
return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName) |
ed13c3ab |
}
cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
if err != nil {
return err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
}
if strings.HasSuffix(f.Name(), ".key") {
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
if !hasFile(fs, certName) { |
d5e2802e |
return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName) |
ed13c3ab |
}
}
}
return nil
}
|
de5c80b4 |
// Headers returns request modifiers with a User-Agent and metaHeaders
func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier { |
61a49bb6 |
modifiers := []transport.RequestModifier{}
if userAgent != "" {
modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
"User-Agent": []string{userAgent},
})) |
a01cc3ca |
} |
73823e5e |
if metaHeaders != nil {
modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) |
a01cc3ca |
} |
73823e5e |
return modifiers |
a01cc3ca |
}
|
c1be45fa |
// HTTPClient returns an HTTP client structure which uses the given transport |
4fcb9ac4 |
// and contains the necessary headers for redirected requests |
a01cc3ca |
func HTTPClient(transport http.RoundTripper) *http.Client {
return &http.Client{
Transport: transport, |
4fcb9ac4 |
CheckRedirect: addRequiredHeadersToRedirectedRequests, |
a01cc3ca |
} |
05243104 |
}
|
a1245318 |
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 { |
daa89c42 |
if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { |
a1245318 |
return true
}
}
return false
}
|
4fcb9ac4 |
// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
// for redirected requests
func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { |
2a1b7f22 |
if via != nil && via[0] != nil { |
a1245318 |
if trustedLocation(req) && trustedLocation(via[0]) {
req.Header = via[0].Header |
45a29238 |
return nil
}
for k, v := range via[0].Header {
if k != "Authorization" {
for _, vv := range v {
req.Header.Add(k, vv) |
a1245318 |
}
}
} |
2a1b7f22 |
}
return nil
} |
19515a7a |
|
4fcb9ac4 |
// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
// default TLS configuration. |
19515a7a |
func NewTransport(tlsConfig *tls.Config) *http.Transport {
if tlsConfig == nil { |
7a8c7b47 |
tlsConfig = tlsconfig.ServerDefault() |
19515a7a |
} |
20702708 |
direct := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: direct.Dial, |
19515a7a |
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
DisableKeepAlives: true,
} |
20702708 |
proxyDialer, err := sockets.DialerFromEnvironment(direct)
if err == nil {
base.Dial = proxyDialer.Dial
}
return base |
19515a7a |
} |