Skip to content

Commit 3e23b67

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

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

+11450
-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

+247-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,205 @@ 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+
for regName := range btoml.GetArray("registry").(*toml.Tree).Values() {
341+
regConf := btoml.GetPath([]string{"registry", regName}).(*toml.Tree)
342+
if regConf == nil {
343+
continue
344+
}
345+
346+
// Create registry certs folder
347+
regCertsDir := path.Join(tmpBuildKitCertsDir, regName)
348+
if err := os.Mkdir(regCertsDir, 0755); err != nil {
349+
return err
350+
}
351+
352+
regCAs := regConf.GetArray("ca").([]string)
353+
if len(regCAs) > 0 {
354+
var cas []string
355+
for _, ca := range regCAs {
356+
cas = append(cas, path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(ca)))
357+
if err := copyfile(ca, path.Join(regCertsDir, path.Base(ca))); err != nil {
358+
return err
359+
}
360+
}
361+
regConf.Set("ca", cas)
362+
}
363+
364+
regKeyPairs := regConf.GetArray("keypair").([]*toml.Tree)
365+
if len(regKeyPairs) > 0 {
366+
for _, kp := range regKeyPairs {
367+
key := kp.Get("key").(string)
368+
if len(key) > 0 {
369+
kp.Set("key", path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(key)))
370+
if err := copyfile(key, path.Join(regCertsDir, path.Base(key))); err != nil {
371+
return err
372+
}
373+
}
374+
cert := kp.Get("cert").(string)
375+
if len(cert) > 0 {
376+
kp.Set("cert", path.Join(containerBuildKitConfigDir, "certs", regName, path.Base(cert)))
377+
if err := copyfile(cert, path.Join(regCertsDir, path.Base(cert))); err != nil {
378+
return err
379+
}
380+
}
381+
}
382+
}
383+
}
384+
385+
// Write BuildKit config
386+
bkfile, err := os.OpenFile(path.Join(tmpBuildKitConfigDir, "buildkitd.toml"), os.O_CREATE|os.O_WRONLY, 0600)
387+
if err != nil {
388+
return err
389+
}
390+
_, err = btoml.WriteTo(bkfile)
391+
if err != nil {
392+
return err
393+
}
394+
395+
return d.copyToContainer(ctx, tmpDir+"/.", "/")
396+
}
397+
398+
// loadBuildKitConfigTree loads toml BuildKit config tree
399+
func loadBuildKitConfigTree(fp string) (*toml.Tree, error) {
400+
f, err := os.Open(fp)
401+
if err != nil {
402+
if errors.Is(err, os.ErrNotExist) {
403+
return nil, nil
404+
}
405+
return nil, errors.Wrapf(err, "failed to load config from %s", fp)
406+
}
407+
defer f.Close()
408+
t, err := toml.LoadReader(f)
409+
if err != nil {
410+
return t, errors.Wrap(err, "failed to parse config")
411+
}
412+
return t, nil
413+
}
414+
208415
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
209416
execConfig := types.ExecConfig{
210417
Cmd: cmd,
@@ -366,29 +573,6 @@ func (d *demux) Read(dt []byte) (int, error) {
366573
return d.Reader.Read(dt)
367574
}
368575

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-
392576
type logWriter struct {
393577
logger progress.SubLogger
394578
stream int
@@ -398,3 +582,35 @@ func (l *logWriter) Write(dt []byte) (int, error) {
398582
l.logger.Log(l.stream, dt)
399583
return len(dt), nil
400584
}
585+
586+
func copyfile(src string, dst string) error {
587+
si, err := os.Stat(src)
588+
if err != nil {
589+
return err
590+
}
591+
592+
if si.Mode()&os.ModeSymlink != 0 {
593+
if src, err = os.Readlink(src); err != nil {
594+
return err
595+
}
596+
si, err = os.Stat(src)
597+
if err != nil {
598+
return err
599+
}
600+
}
601+
602+
sf, err := os.Open(src)
603+
if err != nil {
604+
return err
605+
}
606+
defer sf.Close()
607+
608+
df, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, si.Mode())
609+
if err != nil {
610+
return err
611+
}
612+
defer df.Close()
613+
614+
_, err = io.Copy(df, sf)
615+
return err
616+
}

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)