mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-10-30 18:18:26 +00:00
202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
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
|
|
}
|