Skip to content

Commit

Permalink
buildctl: Add configured TLS certificate to trust store when making c…
Browse files Browse the repository at this point in the history
…alls to registry auth

Signed-off-by: njucjc <njucjc@gmail.com>
  • Loading branch information
njucjc committed Sep 8, 2023
1 parent 3d44ec2 commit f2d23db
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 14 deletions.
24 changes: 19 additions & 5 deletions cmd/buildctl/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (

"github.com/containerd/continuity"
"github.com/docker/cli/cli/config"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/sync/errgroup"

"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/cmd/buildctl/build"
Expand All @@ -24,10 +29,6 @@ import (
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/progress/progresswriter"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/sync/errgroup"
)

var buildCommand = cli.Command{
Expand Down Expand Up @@ -105,6 +106,18 @@ var buildCommand = cli.Command{
Name: "ref-file",
Usage: "Write build ref to a file",
},
cli.StringFlag{
Name: "registry-auth-tlscacert",
Usage: "CA certificate to validate TLS when authenticating with registries",
},
cli.StringFlag{
Name: "registry-auth-tlscert",
Usage: "Client certificate to validate TLS when authenticating with registries",
},
cli.StringFlag{
Name: "registry-auth-tlskey",
Usage: "Client key to validate TLS when authenticating with registries",
},
},
}

Expand Down Expand Up @@ -158,7 +171,8 @@ func buildAction(clicontext *cli.Context) error {
}

dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(dockerConfig)}
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(dockerConfig,
clicontext.String("registry-auth-tlscacert"), clicontext.String("registry-auth-tlscert"), clicontext.String("registry-auth-tlskey"))}

if ssh := clicontext.StringSlice("ssh"); len(ssh) > 0 {
configs, err := build.ParseSSH(ssh)
Expand Down
62 changes: 55 additions & 7 deletions session/auth/authprovider/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"sync"
Expand All @@ -18,26 +21,30 @@ import (
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"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"

"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth"
"github.com/moby/buildkit/util/progress/progresswriter"
)

const defaultExpiration = 60
const dockerHubConfigfileKey = "https://index.docker.io/v1/"
const dockerHubRegistryHost = "registry-1.docker.io"

func NewDockerAuthProvider(cfg *configfile.ConfigFile) session.Attachable {
func NewDockerAuthProvider(cfg *configfile.ConfigFile, tlscacert string, tlscert string, tlskey string) session.Attachable {
return &authProvider{
authConfigCache: map[string]*types.AuthConfig{},
config: cfg,
seeds: &tokenSeeds{dir: config.Dir()},
loggerCache: map[string]struct{}{},
tlscacert: tlscacert,
tlscert: tlscert,
tlskey: tlskey,
}
}

Expand All @@ -47,6 +54,9 @@ type authProvider struct {
seeds *tokenSeeds
logger progresswriter.Logger
loggerCache map[string]struct{}
tlscacert string
tlscert string
tlskey string

// The need for this mutex is not well understood.
// Without it, the docker cli on OS X hangs when
Expand Down Expand Up @@ -89,6 +99,12 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ
Secret: creds.Secret,
}

var httpClient = http.DefaultClient
if tc, err := ap.tlsConfig(); err == nil {
httpClient.Transport = http.DefaultTransport
httpClient.Transport.(*http.Transport).TLSClientConfig = tc
}

if creds.Secret != "" {
done := func(progresswriter.SubLogger) error {
return err
Expand All @@ -103,15 +119,15 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ
}
ap.mu.Unlock()
// credential information is provided, use oauth POST endpoint
resp, err := authutil.FetchTokenWithOAuth(ctx, http.DefaultClient, nil, "buildkit-client", to)
resp, err := authutil.FetchTokenWithOAuth(ctx, httpClient, nil, "buildkit-client", to)
if err != nil {
var errStatus remoteserrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
// Registries without support for POST may return 404 for POST /v2/token.
// 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.FetchToken(ctx, http.DefaultClient, nil, to)
resp, err := authutil.FetchToken(ctx, httpClient, nil, to)
if err != nil {
return nil, err
}
Expand All @@ -123,13 +139,45 @@ func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequ
return toTokenResponse(resp.AccessToken, resp.IssuedAt, resp.ExpiresIn), nil
}
// do request anonymously
resp, err := authutil.FetchToken(ctx, http.DefaultClient, nil, to)
resp, err := authutil.FetchToken(ctx, httpClient, 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) tlsConfig() (*tls.Config, error) {
if ap.tlscacert == "" && (ap.tlscert == "" || ap.tlskey == "") {
return nil, nil
}
tc := &tls.Config{}
if ap.tlscacert != "" {
caCert, err := os.ReadFile(ap.tlscacert)
if err != nil {
return nil, err
}
rootCAs, err := x509.SystemCertPool()
if err != nil {
if runtime.GOOS == "windows" {
rootCAs = x509.NewCertPool()
} else {
return nil, errors.Wrapf(err, "unable to get system cert pool")
}
}
rootCAs.AppendCertsFromPEM(caCert)
tc.RootCAs = rootCAs
}

if ap.tlscert != "" && ap.tlskey != "" {
cert, err := tls.LoadX509KeyPair(ap.tlscert, ap.tlskey)
if err != nil {
return nil, errors.Wrapf(err, "failed to load keypair for %s", ap.tlscert)
}
tc.Certificates = append(tc.Certificates, cert)
}
return tc, nil
}

func (ap *authProvider) credentials(host string) (*auth.CredentialsResponse, error) {
ac, err := ap.getAuthConfig(host)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions session/auth/authprovider/authprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (

"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/moby/buildkit/session/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/moby/buildkit/session/auth"
)

func TestFetchTokenCaching(t *testing.T) {
Expand All @@ -17,7 +18,7 @@ func TestFetchTokenCaching(t *testing.T) {
dockerHubConfigfileKey: {Username: "user", RegistryToken: "hunter2"},
},
}
p := NewDockerAuthProvider(cfg).(*authProvider)
p := NewDockerAuthProvider(cfg, "", "", "").(*authProvider)
res, err := p.FetchToken(context.Background(), &auth.FetchTokenRequest{Host: dockerHubRegistryHost})
require.NoError(t, err)
assert.Equal(t, "hunter2", res.Token)
Expand Down

0 comments on commit f2d23db

Please sign in to comment.