Skip to content

Commit 6f611e6

Browse files
committed
container driver: copy ca and user tls registries certs
Signed-off-by: CrazyMax <[email protected]>
1 parent 868610e commit 6f611e6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+11453
-31
lines changed

docs/reference/buildx_create.md

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ Specifies the configuration file for the buildkitd daemon to use. The configurat
8484
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
8585
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
8686

87+
Also note that if you create a `docker-container` builder and have specified
88+
certificates for registries in the `buildkitd.toml` configuration, these will
89+
be copied into the container under `/etc/buildkit/certs`.
90+
8791
### <a name="driver"></a> Set the builder driver to use (--driver)
8892

8993
```

driver/docker-container/driver.go

+250-31
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
package docker
22

33
import (
4-
"archive/tar"
54
"bytes"
65
"context"
76
"io"
87
"io/ioutil"
98
"net"
109
"os"
10+
"path"
11+
"path/filepath"
1112
"time"
1213

1314
"github.com/docker/buildx/driver"
1415
"github.com/docker/buildx/driver/bkimage"
1516
"github.com/docker/buildx/util/imagetools"
1617
"github.com/docker/buildx/util/progress"
18+
"github.com/docker/cli/cli/command"
1719
"github.com/docker/docker/api/types"
1820
dockertypes "github.com/docker/docker/api/types"
1921
"github.com/docker/docker/api/types/container"
2022
"github.com/docker/docker/api/types/mount"
2123
"github.com/docker/docker/api/types/network"
2224
dockerclient "github.com/docker/docker/client"
25+
dockerarchive "github.com/docker/docker/pkg/archive"
2326
"github.com/docker/docker/pkg/stdcopy"
27+
"github.com/docker/docker/pkg/system"
2428
"github.com/moby/buildkit/client"
2529
"github.com/moby/buildkit/util/tracing/detect"
30+
"github.com/pelletier/go-toml"
2631
"github.com/pkg/errors"
2732
)
2833

@@ -33,7 +38,8 @@ const (
3338
// stores its state. The container driver creates a Linux container, so
3439
// this should match the location for Linux, as defined in:
3540
// https://github.com/moby/buildkit/blob/v0.9.0/util/appdefaults/appdefaults_unix.go#L11-L15
36-
containerBuildKitRootDir = "/var/lib/buildkit"
41+
containerBuildKitRootDir = "/var/lib/buildkit"
42+
containerBuildKitConfigDir = "/etc/buildkit"
3743
)
3844

3945
type Driver struct {
@@ -139,12 +145,14 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
139145
return err
140146
}
141147
if f := d.InitConfig.ConfigFile; f != "" {
142-
buf, err := readFileToTar(f)
143-
if err != nil {
144-
return err
145-
}
146-
if err := d.DockerAPI.CopyToContainer(ctx, d.Name, "/", buf, dockertypes.CopyToContainerOptions{}); err != nil {
147-
return err
148+
if _, err := os.Stat(d.InitConfig.ConfigFile); err == nil {
149+
if err := d.copyBuildKitConfToContainer(ctx, f); err != nil {
150+
return err
151+
}
152+
} else if errors.Is(err, os.ErrNotExist) {
153+
return errors.Wrapf(err, "buildkit configuration file not found: %s", d.InitConfig.ConfigFile)
154+
} else {
155+
return errors.Wrapf(err, "invalid buildkit configuration file: %s", d.InitConfig.ConfigFile)
148156
}
149157
}
150158
if err := d.start(ctx, l); err != nil {
@@ -205,6 +213,208 @@ func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
205213
return rc.Close()
206214
}
207215

216+
// copyToContainer is based on the implementation from docker/cli
217+
// https://github.com/docker/cli/blob/master/cli/command/container/cp.go
218+
func (d *Driver) copyToContainer(ctx context.Context, srcPath string, dstPath string) error {
219+
var err error
220+
221+
// Get an absolute source path.
222+
srcPath, err = resolveLocalPath(srcPath)
223+
if err != nil {
224+
return err
225+
}
226+
227+
// Prepare the destination.
228+
dstInfo := dockerarchive.CopyInfo{Path: dstPath}
229+
dstStat, err := d.DockerAPI.ContainerStatPath(ctx, d.Name, dstPath)
230+
231+
// If the destination is a symbolic link, we should evaluate it.
232+
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
233+
linkTarget := dstStat.LinkTarget
234+
if !system.IsAbs(linkTarget) {
235+
dstParent, _ := dockerarchive.SplitPathDirEntry(dstPath)
236+
linkTarget = filepath.Join(dstParent, linkTarget)
237+
}
238+
dstInfo.Path = linkTarget
239+
if dstStat, err = d.DockerAPI.ContainerStatPath(ctx, d.Name, linkTarget); err != nil {
240+
return err
241+
}
242+
}
243+
244+
// Validate the destination path.
245+
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
246+
return errors.Wrapf(err, `destination "%s:%s" must be a directory or a regular file`, d.Name, dstPath)
247+
}
248+
249+
// Ignore any error and assume that the parent directory of the destination
250+
// path exists, in which case the copy may still succeed. If there is any
251+
// type of conflict (e.g., non-directory overwriting an existing directory
252+
// or vice versa) the extraction will fail. If the destination simply did
253+
// not exist, but the parent directory does, the extraction will still
254+
// succeed.
255+
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
256+
257+
// Prepare source copy info.
258+
srcInfo, err := dockerarchive.CopyInfoSourcePath(srcPath, true)
259+
if err != nil {
260+
return err
261+
}
262+
263+
srcArchive, err := dockerarchive.TarResource(srcInfo)
264+
if err != nil {
265+
return err
266+
}
267+
defer srcArchive.Close()
268+
269+
// With the stat info about the local source as well as the
270+
// destination, we have enough information to know whether we need to
271+
// alter the archive that we upload so that when the server extracts
272+
// it to the specified directory in the container we get the desired
273+
// copy behavior.
274+
275+
// See comments in the implementation of `dockerarchive.PrepareArchiveCopy`
276+
// for exactly what goes into deciding how and whether the source
277+
// archive needs to be altered for the correct copy behavior when it is
278+
// extracted. This function also infers from the source and destination
279+
// info which directory to extract to, which may be the parent of the
280+
// destination that the user specified.
281+
dstDir, preparedArchive, err := dockerarchive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
282+
if err != nil {
283+
return err
284+
}
285+
defer preparedArchive.Close()
286+
287+
return d.DockerAPI.CopyToContainer(ctx, d.Name, dstDir, preparedArchive, dockertypes.CopyToContainerOptions{
288+
AllowOverwriteDirWithFile: false,
289+
})
290+
}
291+
292+
func resolveLocalPath(localPath string) (absPath string, err error) {
293+
if absPath, err = filepath.Abs(localPath); err != nil {
294+
return
295+
}
296+
return dockerarchive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
297+
}
298+
299+
// copyBuildKitConfToContainer copy BuildKit config and registry
300+
// certificates to the containers
301+
func (d *Driver) copyBuildKitConfToContainer(ctx context.Context, bkconfig string) error {
302+
// Load BuildKit config tree
303+
btoml, err := loadBuildKitConfigTree(bkconfig)
304+
if err != nil {
305+
return err
306+
}
307+
308+
// Temp dir that will be copied to the container
309+
tmpDir, err := os.MkdirTemp("", "buildkitd-config")
310+
if err != nil {
311+
return err
312+
}
313+
defer os.RemoveAll(tmpDir)
314+
315+
// Create BuildKit config folders
316+
tmpBuildKitConfigDir := path.Join(tmpDir, containerBuildKitConfigDir)
317+
tmpBuildKitCertsDir := path.Join(tmpBuildKitConfigDir, "certs")
318+
if err := os.MkdirAll(tmpBuildKitCertsDir, 0755); err != nil {
319+
return err
320+
}
321+
322+
// Iterate through registry config to copy certs and update
323+
// BuildKit config with the underlying certs' path in the container.
324+
//
325+
// The following BuildKit config:
326+
//
327+
// [registry."myregistry.io"]
328+
// ca=["/etc/config/myca.pem"]
329+
// [[registry."myregistry.io".keypair]]
330+
// key="/etc/config/key.pem"
331+
// cert="/etc/config/cert.pem"
332+
//
333+
// will be translated in the container as:
334+
//
335+
// [registry."myregistry.io"]
336+
// ca=["/etc/buildkit/certs/myregistry.io/myca.pem"]
337+
// [[registry."myregistry.io".keypair]]
338+
// key="/etc/buildkit/certs/myregistry.io/key.pem"
339+
// cert="/etc/buildkit/certs/myregistry.io/cert.pem"
340+
registry := btoml.GetArray("registry").(*toml.Tree)
341+
if registry != nil {
342+
for regName := range registry.Values() {
343+
regConf := btoml.GetPath([]string{"registry", regName}).(*toml.Tree)
344+
if regConf == nil {
345+
continue
346+
}
347+
regCertsDir := path.Join(tmpBuildKitCertsDir, regName)
348+
if err := os.Mkdir(regCertsDir, 0755); err != nil {
349+
return err
350+
}
351+
regCAs := regConf.GetArray("ca").([]string)
352+
if len(regCAs) > 0 {
353+
var cas []string
354+
for _, ca := range regCAs {
355+
cas = append(cas, path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(ca)))
356+
if err := copyfile(ca, path.Join(regCertsDir, path.Base(ca))); err != nil {
357+
return err
358+
}
359+
}
360+
regConf.Set("ca", cas)
361+
}
362+
regKeyPairs := regConf.GetArray("keypair").([]*toml.Tree)
363+
if len(regKeyPairs) == 0 {
364+
continue
365+
}
366+
for _, kp := range regKeyPairs {
367+
if kp == nil {
368+
continue
369+
}
370+
key := kp.Get("key").(string)
371+
if len(key) > 0 {
372+
kp.Set("key", path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(key)))
373+
if err := copyfile(key, path.Join(regCertsDir, path.Base(key))); err != nil {
374+
return err
375+
}
376+
}
377+
cert := kp.Get("cert").(string)
378+
if len(cert) > 0 {
379+
kp.Set("cert", path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(cert)))
380+
if err := copyfile(cert, path.Join(regCertsDir, path.Base(cert))); err != nil {
381+
return err
382+
}
383+
}
384+
}
385+
}
386+
}
387+
388+
// Write BuildKit config
389+
bkfile, err := os.OpenFile(path.Join(tmpBuildKitConfigDir, "buildkitd.toml"), os.O_CREATE|os.O_WRONLY, 0600)
390+
if err != nil {
391+
return err
392+
}
393+
_, err = btoml.WriteTo(bkfile)
394+
if err != nil {
395+
return err
396+
}
397+
398+
return d.copyToContainer(ctx, tmpDir+"/.", "/")
399+
}
400+
401+
// loadBuildKitConfigTree loads toml BuildKit config tree
402+
func loadBuildKitConfigTree(fp string) (*toml.Tree, error) {
403+
f, err := os.Open(fp)
404+
if err != nil {
405+
if errors.Is(err, os.ErrNotExist) {
406+
return nil, nil
407+
}
408+
return nil, errors.Wrapf(err, "failed to load config from %s", fp)
409+
}
410+
defer f.Close()
411+
t, err := toml.LoadReader(f)
412+
if err != nil {
413+
return t, errors.Wrap(err, "failed to parse config")
414+
}
415+
return t, nil
416+
}
417+
208418
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
209419
execConfig := types.ExecConfig{
210420
Cmd: cmd,
@@ -366,29 +576,6 @@ func (d *demux) Read(dt []byte) (int, error) {
366576
return d.Reader.Read(dt)
367577
}
368578

369-
func readFileToTar(fn string) (*bytes.Buffer, error) {
370-
buf := bytes.NewBuffer(nil)
371-
tw := tar.NewWriter(buf)
372-
dt, err := ioutil.ReadFile(fn)
373-
if err != nil {
374-
return nil, err
375-
}
376-
if err := tw.WriteHeader(&tar.Header{
377-
Name: "/etc/buildkit/buildkitd.toml",
378-
Size: int64(len(dt)),
379-
Mode: 0644,
380-
}); err != nil {
381-
return nil, err
382-
}
383-
if _, err := tw.Write(dt); err != nil {
384-
return nil, err
385-
}
386-
if err := tw.Close(); err != nil {
387-
return nil, err
388-
}
389-
return buf, nil
390-
}
391-
392579
type logWriter struct {
393580
logger progress.SubLogger
394581
stream int
@@ -398,3 +585,35 @@ func (l *logWriter) Write(dt []byte) (int, error) {
398585
l.logger.Log(l.stream, dt)
399586
return len(dt), nil
400587
}
588+
589+
func copyfile(src string, dst string) error {
590+
si, err := os.Stat(src)
591+
if err != nil {
592+
return err
593+
}
594+
595+
if si.Mode()&os.ModeSymlink != 0 {
596+
if src, err = os.Readlink(src); err != nil {
597+
return err
598+
}
599+
si, err = os.Stat(src)
600+
if err != nil {
601+
return err
602+
}
603+
}
604+
605+
sf, err := os.Open(src)
606+
if err != nil {
607+
return err
608+
}
609+
defer sf.Close()
610+
611+
df, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, si.Mode())
612+
if err != nil {
613+
return err
614+
}
615+
defer df.Close()
616+
617+
_, err = io.Copy(df, sf)
618+
return err
619+
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
github.com/moby/buildkit v0.9.1-0.20211008210008-ba673bbdab4f
3535
github.com/opencontainers/go-digest v1.0.0
3636
github.com/opencontainers/image-spec v1.0.2-0.20210819154149-5ad6f50d6283
37+
github.com/pelletier/go-toml v1.9.4
3738
github.com/pkg/errors v0.9.1
3839
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
3940
github.com/sirupsen/logrus v1.8.1

0 commit comments

Comments
 (0)