Files
buildkit/session/auth/auth.go
Tonis Tiigi b5d7ecc353 session: add timeout for credentialsfunc
Ensure build does not appear deadlocked for some
credentials helper issue.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-04-16 16:45:04 -07:00

142 lines
3.9 KiB
Go

package auth
import (
"context"
"crypto/rand"
"crypto/subtle"
"sync"
"time"
"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"
)
const sessionAuthTimeout = 60 * time.Second
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(ctx context.Context, sm *session.Manager, g session.Group) func(string) (session, username, secret string, err error) {
return func(host string) (string, string, string, error) {
ctx, cancel := context.WithTimeoutCause(ctx, sessionAuthTimeout, errors.Wrap(context.DeadlineExceeded, "resolving credentials from session"))
defer cancel()
var sessionID, user, secret string
err := sm.Any(ctx, g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err := client.Credentials(ctx, &CredentialsRequest{
Host: host,
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented {
return nil
}
return err
}
sessionID = id
user = resp.Username
secret = resp.Secret
return nil
})
if err != nil {
return "", "", "", err
}
return sessionID, user, secret, nil
}
}
func FetchToken(ctx context.Context, req *FetchTokenRequest, sm *session.Manager, g session.Group) (resp *FetchTokenResponse, err error) {
ctx, cancel := context.WithTimeoutCause(ctx, sessionAuthTimeout, errors.Wrap(context.DeadlineExceeded, "fetching auth token from session"))
defer cancel()
err = sm.Any(ctx, 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(ctx context.Context, host string, pubKey *[32]byte, sm *session.Manager, g session.Group) (sessionID string, ok bool, err error) {
ctx, cancel := context.WithTimeoutCause(ctx, sessionAuthTimeout, errors.Wrap(context.DeadlineExceeded, "verifying token authority from session"))
defer cancel()
var verified bool
err = sm.Any(ctx, 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(ctx context.Context, host string, sm *session.Manager, g session.Group) (sessionID string, pubKey *[32]byte, err error) {
ctx, cancel := context.WithTimeoutCause(ctx, sessionAuthTimeout, errors.Wrap(context.DeadlineExceeded, "getting token authority from session"))
defer cancel()
err = sm.Any(ctx, 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
}