support registry auth
HACK: the way buildkit works, we can only supply an Auth Provider for the entirety of the build session (`dagger up`). Therefore, we start by scanning all auth in the entire Cue tree and supply an auth provider for all of them. Drawbacks: - As soon as you add `auth` in a Pipeline for a registry, all other Pipelines have access to the same registry - You can't use different credentials for the same registry Fixes #301 Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
_ "github.com/moby/buildkit/client/connhelper/dockercontainer" // import the container connection driver
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
|
||||
// docker output
|
||||
"dagger.io/go/pkg/buildkitd"
|
||||
@@ -101,9 +102,13 @@ func (c *Client) buildfn(ctx context.Context, deployment *Deployment, fn ClientD
|
||||
localdirs[label] = abs
|
||||
}
|
||||
|
||||
// buildkit auth provider (registry)
|
||||
auth := newRegistryAuthProvider()
|
||||
|
||||
// Setup solve options
|
||||
opts := bk.SolveOpt{
|
||||
LocalDirs: localdirs,
|
||||
Session: []session.Attachable{auth},
|
||||
}
|
||||
|
||||
// Call buildkit solver
|
||||
@@ -113,13 +118,16 @@ func (c *Client) buildfn(ctx context.Context, deployment *Deployment, fn ClientD
|
||||
Msg("spawning buildkit job")
|
||||
|
||||
resp, err := c.c.Build(ctx, opts, "", func(ctx context.Context, gw bkgw.Client) (*bkgw.Result, error) {
|
||||
s := NewSolver(c.c, gw, ch, c.noCache)
|
||||
s := NewSolver(c.c, gw, ch, opts.Session, c.noCache)
|
||||
|
||||
lg.Debug().Msg("loading configuration")
|
||||
if err := deployment.LoadPlan(ctx, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lg.Debug().Msg("loading registry credentials from plan")
|
||||
auth.SetCredentials(deployment.RegistryCredentials(ctx))
|
||||
|
||||
// Compute output overlay
|
||||
if fn != nil {
|
||||
if err := fn(ctx, deployment, s); err != nil {
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"dagger.io/go/dagger/compiler"
|
||||
"dagger.io/go/stdlib"
|
||||
|
||||
bkauth "github.com/moby/buildkit/session/auth"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
@@ -113,7 +114,7 @@ func (d *Deployment) LoadPlan(ctx context.Context, s Solver) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scan all scripts in the deployment for references to local directories (do:"local"),
|
||||
// Scan all pipelines in the deployment for references to local directories (do:"local"),
|
||||
// and return all referenced directory names.
|
||||
// This is used by clients to grant access to local directories when they are referenced
|
||||
// by user-specified scripts.
|
||||
@@ -164,6 +165,75 @@ func (d *Deployment) LocalDirs() map[string]string {
|
||||
return dirs
|
||||
}
|
||||
|
||||
// Scan all pipelines in the deployment for registry credentials
|
||||
func (d *Deployment) RegistryCredentials(ctx context.Context) map[string]*bkauth.CredentialsResponse {
|
||||
credentials := map[string]*bkauth.CredentialsResponse{}
|
||||
|
||||
src, err := compiler.InstanceMerge(d.plan, d.input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
flow := cueflow.New(
|
||||
&cueflow.Config{},
|
||||
src.CueInst(),
|
||||
newTaskFunc(src.CueInst(), noOpRunner),
|
||||
)
|
||||
|
||||
authenticatedOps := map[string]struct{}{
|
||||
"push-container": {},
|
||||
"fetch-container": {},
|
||||
"docker-build": {},
|
||||
}
|
||||
|
||||
for _, t := range flow.Tasks() {
|
||||
v := compiler.Wrap(t.Value(), src.CueInst())
|
||||
Analyze(
|
||||
func(op *compiler.Value) error {
|
||||
do, err := op.Lookup("do").String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := authenticatedOps[do]; !ok {
|
||||
return nil
|
||||
}
|
||||
auth, err := op.Lookup("auth").Fields()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range auth {
|
||||
host := a.Label
|
||||
|
||||
username, err := a.Value.Lookup("username").String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret, err := a.Value.Lookup("secret").String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.
|
||||
Ctx(ctx).
|
||||
Debug().
|
||||
Str("component", t.Path().String()).
|
||||
Str("host", host).
|
||||
Msg("loading registry credentials")
|
||||
|
||||
credentials[host] = &bkauth.CredentialsResponse{
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
v.Lookup("#up"),
|
||||
)
|
||||
}
|
||||
|
||||
return credentials
|
||||
}
|
||||
|
||||
// Up missing values in deployment configuration, and write them to state.
|
||||
func (d *Deployment) Up(ctx context.Context, s Solver) error {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "deployment.Up")
|
||||
|
73
dagger/registryauth.go
Normal file
73
dagger/registryauth.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package dagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
bkauth "github.com/moby/buildkit/session/auth"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// registryAuthProvider is a buildkit provider for registry authentication
|
||||
// Adapted from: https://github.com/moby/buildkit/blob/master/session/auth/authprovider/authprovider.go
|
||||
type registryAuthProvider struct {
|
||||
credentials map[string]*bkauth.CredentialsResponse
|
||||
}
|
||||
|
||||
func newRegistryAuthProvider() *registryAuthProvider {
|
||||
return ®istryAuthProvider{}
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) SetCredentials(credentials map[string]*bkauth.CredentialsResponse) {
|
||||
a.credentials = credentials
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) Register(server *grpc.Server) {
|
||||
bkauth.RegisterAuthServer(server, a)
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) Credentials(ctx context.Context, req *bkauth.CredentialsRequest) (*bkauth.CredentialsResponse, error) {
|
||||
reqURL, err := parseAuthHost(req.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for authHost, auth := range a.credentials {
|
||||
u, err := parseAuthHost(authHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Host == reqURL.Host {
|
||||
return auth, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &bkauth.CredentialsResponse{}, nil
|
||||
}
|
||||
|
||||
func parseAuthHost(host string) (*url.URL, error) {
|
||||
if host == "registry-1.docker.io" {
|
||||
host = "https://index.docker.io/v1/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
|
||||
host = "https://" + host
|
||||
}
|
||||
return url.Parse(host)
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) FetchToken(ctx context.Context, req *bkauth.FetchTokenRequest) (rr *bkauth.FetchTokenResponse, err error) {
|
||||
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) GetTokenAuthority(ctx context.Context, req *bkauth.GetTokenAuthorityRequest) (*bkauth.GetTokenAuthorityResponse, error) {
|
||||
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
|
||||
}
|
||||
|
||||
func (a *registryAuthProvider) VerifyTokenAuthority(ctx context.Context, req *bkauth.VerifyTokenAuthorityRequest) (*bkauth.VerifyTokenAuthorityResponse, error) {
|
||||
return nil, status.Errorf(codes.Unavailable, "client side tokens not implemented")
|
||||
}
|
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
bkpb "github.com/moby/buildkit/solver/pb"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -23,14 +22,16 @@ type Solver struct {
|
||||
events chan *bk.SolveStatus
|
||||
control *bk.Client
|
||||
gw bkgw.Client
|
||||
session []session.Attachable
|
||||
noCache bool
|
||||
}
|
||||
|
||||
func NewSolver(control *bk.Client, gw bkgw.Client, events chan *bk.SolveStatus, noCache bool) Solver {
|
||||
func NewSolver(control *bk.Client, gw bkgw.Client, events chan *bk.SolveStatus, session []session.Attachable, noCache bool) Solver {
|
||||
return Solver{
|
||||
events: events,
|
||||
control: control,
|
||||
gw: gw,
|
||||
session: session,
|
||||
noCache: noCache,
|
||||
}
|
||||
}
|
||||
@@ -148,9 +149,7 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im
|
||||
|
||||
opts := bk.SolveOpt{
|
||||
Exports: []bk.ExportEntry{output},
|
||||
Session: []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(log.Ctx(ctx)),
|
||||
},
|
||||
Session: s.session,
|
||||
}
|
||||
|
||||
ch := make(chan *bk.SolveStatus)
|
||||
|
Reference in New Issue
Block a user