mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-11-04 07:38:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			207 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/hex"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/docker/distribution/reference"
 | 
						|
	"github.com/docker/distribution/registry/client/auth"
 | 
						|
	"github.com/docker/distribution/registry/client/auth/challenge"
 | 
						|
	"github.com/docker/distribution/registry/client/transport"
 | 
						|
	"github.com/docker/docker/cli/trust"
 | 
						|
	notaryClient "github.com/docker/notary/client"
 | 
						|
	"github.com/docker/notary/trustpinning"
 | 
						|
	"github.com/docker/notary/tuf/data"
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
)
 | 
						|
 | 
						|
// TrustedReference parses an image string, and does a notary lookup to verify and retrieve the signed digest reference
 | 
						|
func TrustedReference(image string) (reference.Reference, error) {
 | 
						|
	ref, err := reference.ParseAnyReference(image)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// to mimic docker pull: if we have a digest already, it's implicitly trusted
 | 
						|
	if digestRef, ok := ref.(reference.Digested); ok {
 | 
						|
		return digestRef, nil
 | 
						|
	}
 | 
						|
	// to mimic docker pull: if we have a digest already, it's implicitly trusted
 | 
						|
	if canonicalRef, ok := ref.(reference.Canonical); ok {
 | 
						|
		return canonicalRef, nil
 | 
						|
	}
 | 
						|
 | 
						|
	namedRef, ok := ref.(reference.Named)
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.New("failed to resolve image digest using content trust: reference is not named")
 | 
						|
	}
 | 
						|
	taggedRef, ok := namedRef.(reference.NamedTagged)
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.New("failed to resolve image digest using content trust: reference is not tagged")
 | 
						|
	}
 | 
						|
 | 
						|
	gun := taggedRef.Name()
 | 
						|
	targetName := taggedRef.Tag()
 | 
						|
	server, err := getTrustServer(gun)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	rt, err := GetReadOnlyAuthTransport(server, []string{gun}, "", "", "")
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	nRepo, err := notaryClient.NewNotaryRepository(
 | 
						|
		trustDirectory(),
 | 
						|
		gun,
 | 
						|
		server,
 | 
						|
		rt,
 | 
						|
		nil,
 | 
						|
		trustpinning.TrustPinConfig{},
 | 
						|
	)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	target, err := nRepo.GetTargetByName(targetName, trust.ReleasesRole, data.CanonicalTargetsRole)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// Only get the tag if it's in the top level targets role or the releases delegation role
 | 
						|
	// ignore it if it's in any other delegation roles
 | 
						|
	if target.Role != trust.ReleasesRole && target.Role != data.CanonicalTargetsRole {
 | 
						|
		return nil, errors.New("not signed in valid role")
 | 
						|
	}
 | 
						|
 | 
						|
	h, ok := target.Hashes["sha256"]
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.New("no valid hash, expecting sha256")
 | 
						|
	}
 | 
						|
 | 
						|
	dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
 | 
						|
 | 
						|
	// Allow returning canonical reference with tag and digest
 | 
						|
	return reference.WithDigest(taggedRef, dgst)
 | 
						|
}
 | 
						|
 | 
						|
func getTrustServer(gun string) (string, error) {
 | 
						|
	if strings.HasPrefix(gun, "docker.io/") {
 | 
						|
		return "https://notary.docker.io", nil
 | 
						|
	}
 | 
						|
	return "", errors.New("non-hub images not yet supported")
 | 
						|
}
 | 
						|
 | 
						|
func trustDirectory() string {
 | 
						|
	return filepath.Join(MobyDir, "trust")
 | 
						|
}
 | 
						|
 | 
						|
type credentialStore struct {
 | 
						|
	username      string
 | 
						|
	password      string
 | 
						|
	refreshTokens map[string]string
 | 
						|
}
 | 
						|
 | 
						|
func (tcs *credentialStore) Basic(url *url.URL) (string, string) {
 | 
						|
	return tcs.username, tcs.password
 | 
						|
}
 | 
						|
 | 
						|
// refresh tokens are the long lived tokens that can be used instead of a password
 | 
						|
func (tcs *credentialStore) RefreshToken(u *url.URL, service string) string {
 | 
						|
	return tcs.refreshTokens[service]
 | 
						|
}
 | 
						|
 | 
						|
func (tcs *credentialStore) SetRefreshToken(u *url.URL, service string, token string) {
 | 
						|
	if tcs.refreshTokens != nil {
 | 
						|
		tcs.refreshTokens[service] = token
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetReadOnlyAuthTransport gets the Auth Transport used to communicate with notary
 | 
						|
func GetReadOnlyAuthTransport(server string, scopes []string, username, password, rootCAPath string) (http.RoundTripper, error) {
 | 
						|
	httpsTransport, err := httpsTransport(rootCAPath)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	req, err := http.NewRequest("GET", fmt.Sprintf("%s/v2/", server), nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	pingClient := &http.Client{
 | 
						|
		Transport: httpsTransport,
 | 
						|
		Timeout:   5 * time.Second,
 | 
						|
	}
 | 
						|
	resp, err := pingClient.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	challengeManager := challenge.NewSimpleManager()
 | 
						|
	if err := challengeManager.AddResponse(resp); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	creds := credentialStore{
 | 
						|
		username:      username,
 | 
						|
		password:      password,
 | 
						|
		refreshTokens: make(map[string]string),
 | 
						|
	}
 | 
						|
 | 
						|
	var scopeObjs []auth.Scope
 | 
						|
	for _, scopeName := range scopes {
 | 
						|
		scopeObjs = append(scopeObjs, auth.RepositoryScope{
 | 
						|
			Repository: scopeName,
 | 
						|
			Actions:    []string{"pull"},
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	// allow setting multiple scopes so we don't have to reauth
 | 
						|
	tokenHandler := auth.NewTokenHandlerWithOptions(auth.TokenHandlerOptions{
 | 
						|
		Transport:   httpsTransport,
 | 
						|
		Credentials: &creds,
 | 
						|
		Scopes:      scopeObjs,
 | 
						|
	})
 | 
						|
 | 
						|
	authedTransport := transport.NewTransport(httpsTransport, auth.NewAuthorizer(challengeManager, tokenHandler))
 | 
						|
	return authedTransport, nil
 | 
						|
}
 | 
						|
 | 
						|
func httpsTransport(caFile string) (*http.Transport, error) {
 | 
						|
	tlsConfig := &tls.Config{}
 | 
						|
	transport := http.Transport{
 | 
						|
		Proxy: http.ProxyFromEnvironment,
 | 
						|
		Dial: (&net.Dialer{
 | 
						|
			Timeout:   30 * time.Second,
 | 
						|
			KeepAlive: 30 * time.Second,
 | 
						|
		}).Dial,
 | 
						|
		TLSHandshakeTimeout: 10 * time.Second,
 | 
						|
		TLSClientConfig:     tlsConfig,
 | 
						|
	}
 | 
						|
	// Override with the system cert pool if the caFile was empty
 | 
						|
	if caFile == "" {
 | 
						|
		systemCertPool, err := x509.SystemCertPool()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		transport.TLSClientConfig.RootCAs = systemCertPool
 | 
						|
	} else {
 | 
						|
		certPool := x509.NewCertPool()
 | 
						|
		pems, err := ioutil.ReadFile(caFile)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		certPool.AppendCertsFromPEM(pems)
 | 
						|
		transport.TLSClientConfig.RootCAs = certPool
 | 
						|
	}
 | 
						|
	return &transport, nil
 | 
						|
}
 |