vendor: update buildkit to v0.8

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2020-12-07 22:01:24 -08:00
parent 080e9981c7
commit 69a1419ab1
323 changed files with 20129 additions and 8394 deletions

View File

@ -2,12 +2,29 @@ package auth
import (
"context"
"crypto/subtle"
"math/rand"
"sync"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/sign"
"google.golang.org/grpc/codes"
)
var salt []byte
var saltOnce sync.Once
// getSalt returns unique component per daemon restart to avoid persistent keys
func getSalt() []byte {
saltOnce.Do(func() {
salt = make([]byte, 32)
rand.Read(salt)
})
return salt
}
func CredentialsFunc(sm *session.Manager, g session.Group) func(string) (session, username, secret string, err error) {
return func(host string) (string, string, string, error) {
var sessionID, user, secret string
@ -34,3 +51,80 @@ func CredentialsFunc(sm *session.Manager, g session.Group) func(string) (session
return sessionID, user, secret, nil
}
}
func FetchToken(req *FetchTokenRequest, sm *session.Manager, g session.Group) (resp *FetchTokenResponse, err error) {
err = sm.Any(context.TODO(), g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err = client.FetchToken(ctx, req)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return resp, nil
}
func VerifyTokenAuthority(host string, pubKey *[32]byte, sm *session.Manager, g session.Group) (sessionID string, ok bool, err error) {
var verified bool
err = sm.Any(context.TODO(), g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
payload := make([]byte, 32)
rand.Read(payload)
resp, err := client.VerifyTokenAuthority(ctx, &VerifyTokenAuthorityRequest{
Host: host,
Salt: getSalt(),
Payload: payload,
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented {
return nil
}
return err
}
var dt []byte
dt, ok = sign.Open(nil, resp.Signed, pubKey)
if ok && subtle.ConstantTimeCompare(dt, payload) == 1 {
verified = true
}
sessionID = id
return nil
})
if err != nil {
return "", false, err
}
return sessionID, verified, nil
}
func GetTokenAuthority(host string, sm *session.Manager, g session.Group) (sessionID string, pubKey *[32]byte, err error) {
err = sm.Any(context.TODO(), g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err := client.GetTokenAuthority(ctx, &GetTokenAuthorityRequest{
Host: host,
Salt: getSalt(),
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented || grpcerrors.Code(err) == codes.Unavailable {
return nil
}
return err
}
if len(resp.PublicKey) != 32 {
return errors.Errorf("invalid pubkey length %d", len(pubKey))
}
sessionID = id
pubKey = new([32]byte)
copy((*pubKey)[:], resp.PublicKey)
return nil
})
if err != nil {
return "", nil, err
}
return sessionID, pubKey, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,11 @@ option go_package = "auth";
service Auth{
rpc Credentials(CredentialsRequest) returns (CredentialsResponse);
rpc FetchToken(FetchTokenRequest) returns (FetchTokenResponse);
rpc GetTokenAuthority(GetTokenAuthorityRequest) returns (GetTokenAuthorityResponse);
rpc VerifyTokenAuthority(VerifyTokenAuthorityRequest) returns (VerifyTokenAuthorityResponse);
}
message CredentialsRequest {
string Host = 1;
}
@ -17,3 +19,36 @@ message CredentialsResponse {
string Username = 1;
string Secret = 2;
}
message FetchTokenRequest {
string ClientID = 1;
string Host = 2;
string Realm = 3;
string Service = 4;
repeated string Scopes = 5;
}
message FetchTokenResponse {
string Token = 1;
int64 ExpiresIn = 2; // seconds
int64 IssuedAt = 3; // timestamp
}
message GetTokenAuthorityRequest {
string Host = 1;
bytes Salt = 2;
}
message GetTokenAuthorityResponse {
bytes PublicKey = 1;
}
message VerifyTokenAuthorityRequest {
string Host = 1;
bytes Payload = 2;
bytes Salt = 3;
}
message VerifyTokenAuthorityResponse {
bytes Signed = 1;
}

View File

@ -2,24 +2,46 @@ package authprovider
import (
"context"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
authutil "github.com/containerd/containerd/remotes/docker/auth"
remoteserrors "github.com/containerd/containerd/remotes/errors"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth"
"github.com/moby/buildkit/util/progress/progresswriter"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/sign"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func NewDockerAuthProvider(stderr io.Writer) session.Attachable {
return &authProvider{
config: config.LoadDefaultConfigFile(stderr),
config: config.LoadDefaultConfigFile(stderr),
seeds: &tokenSeeds{dir: config.Dir()},
loggerCache: map[string]struct{}{},
}
}
type authProvider struct {
config *configfile.ConfigFile
config *configfile.ConfigFile
seeds *tokenSeeds
logger progresswriter.Logger
loggerCache map[string]struct{}
// The need for this mutex is not well understood.
// Without it, the docker cli on OS X hangs when
@ -28,17 +50,80 @@ type authProvider struct {
mu sync.Mutex
}
func (ap *authProvider) SetLogger(l progresswriter.Logger) {
ap.mu.Lock()
ap.logger = l
ap.mu.Unlock()
}
func (ap *authProvider) Register(server *grpc.Server) {
auth.RegisterAuthServer(server, ap)
}
func (ap *authProvider) Credentials(ctx context.Context, req *auth.CredentialsRequest) (*auth.CredentialsResponse, error) {
func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequest) (rr *auth.FetchTokenResponse, err error) {
creds, err := ap.credentials(req.Host)
if err != nil {
return nil, err
}
to := authutil.TokenOptions{
Realm: req.Realm,
Service: req.Service,
Scopes: req.Scopes,
Username: creds.Username,
Secret: creds.Secret,
}
if creds.Secret != "" {
done := func(progresswriter.SubLogger) error {
return err
}
defer func() {
err = errors.Wrap(err, "failed to fetch oauth token")
}()
ap.mu.Lock()
name := fmt.Sprintf("[auth] %v token for %s", strings.Join(trimScopePrefix(req.Scopes), " "), req.Host)
if _, ok := ap.loggerCache[name]; !ok {
progresswriter.Wrap(name, ap.logger, done)
}
ap.mu.Unlock()
// try GET first because Docker Hub does not support POST
// switch once support has landed
resp, err := authutil.FetchToken(ctx, http.DefaultClient, nil, to)
if err != nil {
var errStatus remoteserrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
// retry with POST request
// As of September 2017, GCR is known to return 404.
// As of February 2018, JFrog Artifactory is known to return 401.
if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 {
resp, err := authutil.FetchTokenWithOAuth(ctx, http.DefaultClient, nil, "buildkit-client", to)
if err != nil {
return nil, err
}
return toTokenResponse(resp.AccessToken, resp.IssuedAt, resp.ExpiresIn), nil
}
}
return nil, err
}
return toTokenResponse(resp.Token, resp.IssuedAt, resp.ExpiresIn), nil
}
// do request anonymously
resp, err := authutil.FetchToken(ctx, http.DefaultClient, nil, to)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch anonymous token")
}
return toTokenResponse(resp.Token, resp.IssuedAt, resp.ExpiresIn), nil
}
func (ap *authProvider) credentials(host string) (*auth.CredentialsResponse, error) {
ap.mu.Lock()
defer ap.mu.Unlock()
if req.Host == "registry-1.docker.io" {
req.Host = "https://index.docker.io/v1/"
if host == "registry-1.docker.io" {
host = "https://index.docker.io/v1/"
}
ac, err := ap.config.GetAuthConfig(req.Host)
ac, err := ap.config.GetAuthConfig(host)
if err != nil {
return nil, err
}
@ -51,3 +136,85 @@ func (ap *authProvider) Credentials(ctx context.Context, req *auth.CredentialsRe
}
return res, nil
}
func (ap *authProvider) Credentials(ctx context.Context, req *auth.CredentialsRequest) (*auth.CredentialsResponse, error) {
resp, err := ap.credentials(req.Host)
if err != nil || resp.Secret != "" {
ap.mu.Lock()
defer ap.mu.Unlock()
_, ok := ap.loggerCache[req.Host]
ap.loggerCache[req.Host] = struct{}{}
if !ok {
return resp, progresswriter.Wrap(fmt.Sprintf("[auth] sharing credentials for %s", req.Host), ap.logger, func(progresswriter.SubLogger) error {
return err
})
}
}
return resp, err
}
func (ap *authProvider) GetTokenAuthority(ctx context.Context, req *auth.GetTokenAuthorityRequest) (*auth.GetTokenAuthorityResponse, error) {
key, err := ap.getAuthorityKey(req.Host, req.Salt)
if err != nil {
return nil, err
}
return &auth.GetTokenAuthorityResponse{PublicKey: key[32:]}, nil
}
func (ap *authProvider) VerifyTokenAuthority(ctx context.Context, req *auth.VerifyTokenAuthorityRequest) (*auth.VerifyTokenAuthorityResponse, error) {
key, err := ap.getAuthorityKey(req.Host, req.Salt)
if err != nil {
return nil, err
}
priv := new([64]byte)
copy((*priv)[:], key)
return &auth.VerifyTokenAuthorityResponse{Signed: sign.Sign(nil, req.Payload, priv)}, nil
}
func (ap *authProvider) getAuthorityKey(host string, salt []byte) (ed25519.PrivateKey, error) {
if v, err := strconv.ParseBool(os.Getenv("BUILDKIT_NO_CLIENT_TOKEN")); err == nil && v {
return nil, status.Errorf(codes.Unavailable, "client side tokens disabled")
}
creds, err := ap.credentials(host)
if err != nil {
return nil, err
}
seed, err := ap.seeds.getSeed(host)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, salt)
if creds.Secret != "" {
mac.Write(seed)
enc := json.NewEncoder(mac)
enc.Encode(creds)
}
sum := mac.Sum(nil)
return ed25519.NewKeyFromSeed(sum[:ed25519.SeedSize]), nil
}
func toTokenResponse(token string, issuedAt time.Time, expires int) *auth.FetchTokenResponse {
resp := &auth.FetchTokenResponse{
Token: token,
ExpiresIn: int64(expires),
}
if !issuedAt.IsZero() {
resp.IssuedAt = issuedAt.Unix()
}
return resp
}
func trimScopePrefix(scopes []string) []string {
out := make([]string, len(scopes))
for i, s := range scopes {
out[i] = strings.TrimPrefix(s, "repository:")
}
return out
}

View File

@ -0,0 +1,84 @@
package authprovider
import (
"crypto/rand"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/gofrs/flock"
"github.com/pkg/errors"
)
type tokenSeeds struct {
mu sync.Mutex
dir string
m map[string]seed
}
type seed struct {
Seed []byte
}
func (ts *tokenSeeds) getSeed(host string) ([]byte, error) {
ts.mu.Lock()
defer ts.mu.Unlock()
if err := os.MkdirAll(ts.dir, 0755); err != nil {
return nil, err
}
if ts.m == nil {
ts.m = map[string]seed{}
}
l := flock.New(filepath.Join(ts.dir, ".token_seed.lock"))
if err := l.Lock(); err != nil {
if !errors.Is(err, syscall.EROFS) && errors.Is(err, syscall.EPERM) {
return nil, err
}
} else {
defer l.Unlock()
}
fp := filepath.Join(ts.dir, ".token_seed")
// we include client side randomness to avoid chosen plaintext attack from the daemon side
dt, err := ioutil.ReadFile(fp)
if err != nil {
if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, syscall.ENOTDIR) {
return nil, err
}
} else {
if err := json.Unmarshal(dt, &ts.m); err != nil {
return nil, errors.Wrapf(err, "failed to parse %s", fp)
}
}
v, ok := ts.m[host]
if !ok {
v = seed{Seed: newSeed()}
}
ts.m[host] = v
dt, err = json.MarshalIndent(ts.m, "", " ")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(fp, dt, 0600); err != nil {
if !errors.Is(err, syscall.EROFS) && !errors.Is(err, syscall.EPERM) {
return nil, err
}
}
return v.Seed, nil
}
func newSeed() []byte {
b := make([]byte, 16)
rand.Read(b)
return b
}

View File

@ -64,7 +64,7 @@ func grpcClientConn(ctx context.Context, conn net.Conn) (context.Context, *grpc.
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(stream...)))
}
cc, err := grpc.DialContext(ctx, "", dialOpts...)
cc, err := grpc.DialContext(ctx, "localhost", dialOpts...)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create grpc client")
}

View File

@ -6,6 +6,8 @@ import (
"io/ioutil"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/moby/buildkit/session"
@ -139,7 +141,7 @@ func toAgentSource(paths []string) (source, error) {
socket = p
continue
}
keys = true
f, err := os.Open(p)
if err != nil {
return source{}, errors.Wrapf(err, "failed to open %s", p)
@ -151,11 +153,24 @@ func toAgentSource(paths []string) (source, error) {
k, err := ssh.ParseRawPrivateKey(dt)
if err != nil {
// On Windows, os.ModeSocket isn't appropriately set on the file mode.
// https://github.com/golang/go/issues/33357
// If parsing the file fails, check to see if it kind of looks like socket-shaped.
if runtime.GOOS == "windows" && strings.Contains(string(dt), "socket") {
if keys {
return source{}, errors.Errorf("invalid combination of keys and sockets")
}
socket = p
continue
}
return source{}, errors.Wrapf(err, "failed to parse %s", p) // TODO: prompt passphrase?
}
if err := a.Add(agent.AddedKey{PrivateKey: k}); err != nil {
return source{}, errors.Wrapf(err, "failed to add %s to agent", p)
}
keys = true
}
if socket != "" {