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:
parent
52136ab008
commit
c219afdb4b
4 changed files with 123 additions and 52 deletions
|
@ -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
68
docs/reference.go
Normal 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}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue