Skip to content

Commit 3965144

Browse files
committed
container driver: copy ca and user tls registries certs
Signed-off-by: CrazyMax <[email protected]>
1 parent 084b6c0 commit 3965144

Some content is hidden

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

68 files changed

+11469
-33
lines changed

docs/reference/buildx_create.md

+5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ 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, their location
89+
will be translated in the configuration to fit in the container under
90+
`/etc/buildkit/certs`.
91+
8792
### <a name="driver"></a> Set the builder driver to use (--driver)
8893

8994
```

driver/docker-container/driver.go

+92-33
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,37 @@
11
package docker
22

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

1313
"github.com/docker/buildx/driver"
1414
"github.com/docker/buildx/driver/bkimage"
15+
"github.com/docker/buildx/util/bkutil"
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"
2630
"github.com/pkg/errors"
2731
)
2832

2933
const (
3034
volumeStateSuffix = "_state"
31-
32-
// containerStateDir is the location where buildkitd inside the container
33-
// stores its state. The container driver creates a Linux container, so
34-
// this should match the location for Linux, as defined in:
35-
// https://github.com/moby/buildkit/blob/v0.9.0/util/appdefaults/appdefaults_unix.go#L11-L15
36-
containerBuildKitRootDir = "/var/lib/buildkit"
3735
)
3836

3937
type Driver struct {
@@ -119,7 +117,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
119117
{
120118
Type: mount.TypeVolume,
121119
Source: d.Name + volumeStateSuffix,
122-
Target: containerBuildKitRootDir,
120+
Target: bkutil.ContainerBuildKitRootDir,
123121
},
124122
},
125123
}
@@ -139,11 +137,12 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
139137
return err
140138
}
141139
if f := d.InitConfig.ConfigFile; f != "" {
142-
buf, err := readFileToTar(f)
140+
ctnConfigDir, err := bkutil.ContainerConfDir(f)
143141
if err != nil {
144142
return err
145143
}
146-
if err := d.DockerAPI.CopyToContainer(ctx, d.Name, "/", buf, dockertypes.CopyToContainerOptions{}); err != nil {
144+
defer os.RemoveAll(ctnConfigDir)
145+
if err := d.copyToContainer(ctx, ctnConfigDir+"/.", "/"); err != nil {
147146
return err
148147
}
149148
}
@@ -205,6 +204,89 @@ func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
205204
return rc.Close()
206205
}
207206

207+
// copyToContainer is based on the implementation from docker/cli
208+
// https://github.com/docker/cli/blob/master/cli/command/container/cp.go
209+
func (d *Driver) copyToContainer(ctx context.Context, srcPath string, dstPath string) error {
210+
var err error
211+
212+
// Get an absolute source path.
213+
srcPath, err = resolveLocalPath(srcPath)
214+
if err != nil {
215+
return err
216+
}
217+
218+
// Prepare the destination.
219+
dstInfo := dockerarchive.CopyInfo{Path: dstPath}
220+
dstStat, err := d.DockerAPI.ContainerStatPath(ctx, d.Name, dstPath)
221+
222+
// If the destination is a symbolic link, we should evaluate it.
223+
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
224+
linkTarget := dstStat.LinkTarget
225+
if !system.IsAbs(linkTarget) {
226+
dstParent, _ := dockerarchive.SplitPathDirEntry(dstPath)
227+
linkTarget = filepath.Join(dstParent, linkTarget)
228+
}
229+
dstInfo.Path = linkTarget
230+
if dstStat, err = d.DockerAPI.ContainerStatPath(ctx, d.Name, linkTarget); err != nil {
231+
return err
232+
}
233+
}
234+
235+
// Validate the destination path.
236+
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
237+
return errors.Wrapf(err, `destination "%s:%s" must be a directory or a regular file`, d.Name, dstPath)
238+
}
239+
240+
// Ignore any error and assume that the parent directory of the destination
241+
// path exists, in which case the copy may still succeed. If there is any
242+
// type of conflict (e.g., non-directory overwriting an existing directory
243+
// or vice versa) the extraction will fail. If the destination simply did
244+
// not exist, but the parent directory does, the extraction will still
245+
// succeed.
246+
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
247+
248+
// Prepare source copy info.
249+
srcInfo, err := dockerarchive.CopyInfoSourcePath(srcPath, true)
250+
if err != nil {
251+
return err
252+
}
253+
254+
srcArchive, err := dockerarchive.TarResource(srcInfo)
255+
if err != nil {
256+
return err
257+
}
258+
defer srcArchive.Close()
259+
260+
// With the stat info about the local source as well as the
261+
// destination, we have enough information to know whether we need to
262+
// alter the archive that we upload so that when the server extracts
263+
// it to the specified directory in the container we get the desired
264+
// copy behavior.
265+
266+
// See comments in the implementation of `dockerarchive.PrepareArchiveCopy`
267+
// for exactly what goes into deciding how and whether the source
268+
// archive needs to be altered for the correct copy behavior when it is
269+
// extracted. This function also infers from the source and destination
270+
// info which directory to extract to, which may be the parent of the
271+
// destination that the user specified.
272+
dstDir, preparedArchive, err := dockerarchive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
273+
if err != nil {
274+
return err
275+
}
276+
defer preparedArchive.Close()
277+
278+
return d.DockerAPI.CopyToContainer(ctx, d.Name, dstDir, preparedArchive, dockertypes.CopyToContainerOptions{
279+
AllowOverwriteDirWithFile: false,
280+
})
281+
}
282+
283+
func resolveLocalPath(localPath string) (absPath string, err error) {
284+
if absPath, err = filepath.Abs(localPath); err != nil {
285+
return
286+
}
287+
return dockerarchive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
288+
}
289+
208290
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
209291
execConfig := types.ExecConfig{
210292
Cmd: cmd,
@@ -366,29 +448,6 @@ func (d *demux) Read(dt []byte) (int, error) {
366448
return d.Reader.Read(dt)
367449
}
368450

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-
392451
type logWriter struct {
393452
logger progress.SubLogger
394453
stream int

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.20211019185819-8778943ac3da
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

util/bkutil/container.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package bkutil
2+
3+
import (
4+
"io"
5+
"os"
6+
"path"
7+
8+
"github.com/pelletier/go-toml"
9+
"github.com/pkg/errors"
10+
)
11+
12+
const (
13+
// ContainerBuildKitRootDir and ContainerBuildKitConfigDir are the location
14+
// where buildkitd inside the container stores its state. Some drivers
15+
// create a Linux container, so this should match the location for Linux,
16+
// as defined in: https://github.com/moby/buildkit/blob/v0.9.0/util/appdefaults/appdefaults_unix.go#L11-L15
17+
ContainerBuildKitRootDir = "/var/lib/buildkit"
18+
ContainerBuildKitConfigDir = "/etc/buildkit"
19+
)
20+
21+
// ContainerConfDir creates a temp directory with BuildKit config and
22+
// registry certificates ready to be copied to a container.
23+
func ContainerConfDir(bkconfig string) (string, error) {
24+
if _, err := os.Stat(bkconfig); errors.Is(err, os.ErrNotExist) {
25+
return "", errors.Wrapf(err, "buildkit configuration file not found: %s", bkconfig)
26+
} else if err != nil {
27+
return "", errors.Wrapf(err, "invalid buildkit configuration file: %s", bkconfig)
28+
}
29+
30+
// Load BuildKit config tree
31+
btoml, err := loadBuildKitConfigTree(bkconfig)
32+
if err != nil {
33+
return "", err
34+
}
35+
36+
// Temp dir that will be copied to the container
37+
tmpDir, err := os.MkdirTemp("", "buildkitd-config")
38+
if err != nil {
39+
return "", err
40+
}
41+
42+
// Create BuildKit config folders
43+
tmpBuildKitConfigDir := path.Join(tmpDir, ContainerBuildKitConfigDir)
44+
tmpBuildKitCertsDir := path.Join(tmpBuildKitConfigDir, "certs")
45+
if err := os.MkdirAll(tmpBuildKitCertsDir, 0755); err != nil {
46+
return "", err
47+
}
48+
49+
// Iterate through registry config to copy certs and update
50+
// BuildKit config with the underlying certs' path in the container.
51+
//
52+
// The following BuildKit config:
53+
//
54+
// [registry."myregistry.io"]
55+
// ca=["/etc/config/myca.pem"]
56+
// [[registry."myregistry.io".keypair]]
57+
// key="/etc/config/key.pem"
58+
// cert="/etc/config/cert.pem"
59+
//
60+
// will be translated in the container as:
61+
//
62+
// [registry."myregistry.io"]
63+
// ca=["/etc/buildkit/certs/myregistry.io/myca.pem"]
64+
// [[registry."myregistry.io".keypair]]
65+
// key="/etc/buildkit/certs/myregistry.io/key.pem"
66+
// cert="/etc/buildkit/certs/myregistry.io/cert.pem"
67+
if btoml.Has("registry") {
68+
for regName := range btoml.GetArray("registry").(*toml.Tree).Values() {
69+
regConf := btoml.GetPath([]string{"registry", regName}).(*toml.Tree)
70+
if regConf == nil {
71+
continue
72+
}
73+
regCertsDir := path.Join(tmpBuildKitCertsDir, regName)
74+
if err := os.Mkdir(regCertsDir, 0755); err != nil {
75+
return "", err
76+
}
77+
if regConf.Has("ca") {
78+
regCAs := regConf.GetArray("ca").([]string)
79+
if len(regCAs) > 0 {
80+
var cas []string
81+
for _, ca := range regCAs {
82+
cas = append(cas, path.Join(ContainerBuildKitConfigDir, "certs", regName, path.Base(ca)))
83+
if err := copyfile(ca, path.Join(regCertsDir, path.Base(ca))); err != nil {
84+
return "", err
85+
}
86+
}
87+
regConf.Set("ca", cas)
88+
}
89+
}
90+
if regConf.Has("keypair") {
91+
regKeyPairs := regConf.GetArray("keypair").([]*toml.Tree)
92+
if len(regKeyPairs) == 0 {
93+
continue
94+
}
95+
for _, kp := range regKeyPairs {
96+
if kp == nil {
97+
continue
98+
}
99+
key := kp.Get("key").(string)
100+
if len(key) > 0 {
101+
kp.Set("key", path.Join(ContainerBuildKitConfigDir, "certs", regName, path.Base(key)))
102+
if err := copyfile(key, path.Join(regCertsDir, path.Base(key))); err != nil {
103+
return "", err
104+
}
105+
}
106+
cert := kp.Get("cert").(string)
107+
if len(cert) > 0 {
108+
kp.Set("cert", path.Join(ContainerBuildKitConfigDir, "certs", regName, path.Base(cert)))
109+
if err := copyfile(cert, path.Join(regCertsDir, path.Base(cert))); err != nil {
110+
return "", err
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
118+
// Write BuildKit config
119+
bkfile, err := os.OpenFile(path.Join(tmpBuildKitConfigDir, "buildkitd.toml"), os.O_CREATE|os.O_WRONLY, 0600)
120+
if err != nil {
121+
return "", err
122+
}
123+
_, err = btoml.WriteTo(bkfile)
124+
if err != nil {
125+
return "", err
126+
}
127+
128+
return tmpDir, nil
129+
}
130+
131+
func loadBuildKitConfigTree(fp string) (*toml.Tree, error) {
132+
f, err := os.Open(fp)
133+
if err != nil {
134+
if errors.Is(err, os.ErrNotExist) {
135+
return nil, nil
136+
}
137+
return nil, errors.Wrapf(err, "failed to load config from %s", fp)
138+
}
139+
defer f.Close()
140+
t, err := toml.LoadReader(f)
141+
if err != nil {
142+
return t, errors.Wrap(err, "failed to parse config")
143+
}
144+
return t, nil
145+
}
146+
147+
func copyfile(src string, dst string) error {
148+
si, err := os.Stat(src)
149+
if err != nil {
150+
return err
151+
}
152+
if si.Mode()&os.ModeSymlink != 0 {
153+
if src, err = os.Readlink(src); err != nil {
154+
return err
155+
}
156+
si, err = os.Stat(src)
157+
if err != nil {
158+
return err
159+
}
160+
}
161+
sf, err := os.Open(src)
162+
if err != nil {
163+
return err
164+
}
165+
defer sf.Close()
166+
df, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, si.Mode())
167+
if err != nil {
168+
return err
169+
}
170+
defer df.Close()
171+
_, err = io.Copy(df, sf)
172+
return err
173+
}

0 commit comments

Comments
 (0)