mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-22 18:41:37 +00:00
Use docker and notary API for pull
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
ba07bbfb0d
commit
abf0028ee8
@ -10,7 +10,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@ -161,48 +160,22 @@ func dockerRm(container string) error {
|
||||
|
||||
func dockerPull(image string, trustedPull bool) error {
|
||||
log.Debugf("docker pull: %s", image)
|
||||
docker, err := exec.LookPath("docker")
|
||||
if err != nil {
|
||||
return errors.New("Docker does not seem to be installed")
|
||||
}
|
||||
var args = []string{"pull"}
|
||||
if trustedPull {
|
||||
log.Debugf("pulling %s with content trust", image)
|
||||
args = append(args, "--disable-content-trust=false")
|
||||
trustedImg, err := TrustedReference(image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Trusted pull for %s failed: %v", image, err)
|
||||
}
|
||||
image = trustedImg.String()
|
||||
}
|
||||
args = append(args, image)
|
||||
cmd := exec.Command(docker, args...)
|
||||
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
cli, err := dockerClient()
|
||||
if err != nil {
|
||||
return errors.New("could not initialize Docker API client")
|
||||
}
|
||||
|
||||
if _, err := cli.ImagePull(context.Background(), image, types.ImagePullOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ioutil.ReadAll(stdoutPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err := ioutil.ReadAll(stderrPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %s", err, stderr)
|
||||
}
|
||||
|
||||
log.Debugf("docker pull: %s...Done", image)
|
||||
return nil
|
||||
}
|
||||
|
201
cmd/moby/trust.go
Normal file
201
cmd/moby/trust.go
Normal file
@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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(
|
||||
"",
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user