Use notary library for trusted image fetch and signing

Add a trusted flag to force the cli to resolve a tag into a digest via the notary trust library and pull by digest.
On push the flag the trust flag will indicate the digest and size of a manifest should be signed and push to a notary server.
If a tag is given, the cli will resolve the tag into a digest and pull by digest.
After pulling, if a tag is given the cli makes a request to tag the image.

Use certificate directory for notary requests

Read certificates using same logic used by daemon for registry requests.

Catch JSON syntax errors from Notary client

When an uncaught error occurs in Notary it may show up in Docker as a JSON syntax error, causing a confusing error message to the user.
Provide a generic error when a JSON syntax error occurs.

Catch expiration errors and wrap in additional context.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2015-07-15 13:42:45 -07:00
parent 52136ab008
commit c219afdb4b
4 changed files with 123 additions and 52 deletions

View file

@ -38,8 +38,8 @@ const (
IndexServer = DefaultV1Registry + "/v1/" IndexServer = DefaultV1Registry + "/v1/"
// IndexName is the name of the index // IndexName is the name of the index
IndexName = "docker.io" IndexName = "docker.io"
// NotaryServer is the endpoint serving the Notary trust server
// IndexServer = "https://registry-stage.hub.docker.com/v1/" NotaryServer = "https://notary.docker.io"
) )
var ( var (

68
docs/reference.go Normal file
View file

@ -0,0 +1,68 @@
package registry
import (
"strings"
"github.com/docker/distribution/digest"
)
// Reference represents a tag or digest within a repository
type Reference interface {
// HasDigest returns whether the reference has a verifiable
// content addressable reference which may be considered secure.
HasDigest() bool
// ImageName returns an image name for the given repository
ImageName(string) string
// Returns a string representation of the reference
String() string
}
type tagReference struct {
tag string
}
func (tr tagReference) HasDigest() bool {
return false
}
func (tr tagReference) ImageName(repo string) string {
return repo + ":" + tr.tag
}
func (tr tagReference) String() string {
return tr.tag
}
type digestReference struct {
digest digest.Digest
}
func (dr digestReference) HasDigest() bool {
return true
}
func (dr digestReference) ImageName(repo string) string {
return repo + "@" + dr.String()
}
func (dr digestReference) String() string {
return dr.digest.String()
}
// ParseReference parses a reference into either a digest or tag reference
func ParseReference(ref string) Reference {
if strings.Contains(ref, ":") {
dgst, err := digest.ParseDigest(ref)
if err == nil {
return digestReference{digest: dgst}
}
}
return tagReference{tag: ref}
}
// DigestReference creates a digest reference using a digest
func DigestReference(dgst digest.Digest) Reference {
return digestReference{digest: dgst}
}

View file

@ -2,10 +2,14 @@ package registry
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -54,6 +58,54 @@ func hasFile(files []os.FileInfo, name string) bool {
return false return false
} }
// 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 {
// TODO(dmcgowan): Copy system pool
tlsConfig.RootCAs = x509.NewCertPool()
}
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) {
return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
}
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) {
return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
}
}
}
return nil
}
// DockerHeaders returns request modifiers that ensure requests have // DockerHeaders returns request modifiers that ensure requests have
// the User-Agent header set to dockerUserAgent and that metaHeaders // the User-Agent header set to dockerUserAgent and that metaHeaders
// are added. // are added.

View file

@ -2,12 +2,9 @@ package registry
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -110,57 +107,11 @@ func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
tlsConfig.InsecureSkipVerify = !isSecure tlsConfig.InsecureSkipVerify = !isSecure
if isSecure { if isSecure {
hasFile := func(files []os.FileInfo, name string) bool {
for _, f := range files {
if f.Name() == name {
return true
}
}
return false
}
hostDir := filepath.Join(CertsDir, hostname) hostDir := filepath.Join(CertsDir, hostname)
logrus.Debugf("hostDir: %s", hostDir) logrus.Debugf("hostDir: %s", hostDir)
fs, err := ioutil.ReadDir(hostDir) if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil {
if err != nil && !os.IsNotExist(err) {
return nil, err return nil, err
} }
for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") {
if tlsConfig.RootCAs == nil {
// TODO(dmcgowan): Copy system pool
tlsConfig.RootCAs = x509.NewCertPool()
}
logrus.Debugf("crt: %s", filepath.Join(hostDir, f.Name()))
data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
if err != nil {
return nil, 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(hostDir, f.Name()))
if !hasFile(fs, keyName) {
return nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
}
cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), filepath.Join(hostDir, keyName))
if err != nil {
return nil, 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(hostDir, f.Name()))
if !hasFile(fs, certName) {
return nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
}
}
}
} }
return &tlsConfig, nil return &tlsConfig, nil