Skip to content

Commit e5f136d

Browse files
committed
feat: add --with-ssl-cert-file option to build command
This makes the root CAs from the host available to containers during a build. Signed-off-by: Adam Eijdenberg <adam@continusec.com>
1 parent 8fd8abc commit e5f136d

13 files changed

+207
-49
lines changed

define/build.go

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ type CommonBuildOptions struct {
113113
OCIHooksDir []string
114114
// Paths to unmask
115115
Unmasks []string
116+
// WithSSLCertFile specifies whether the host root CAs should be ephemerally mounted within the container
117+
WithSSLCertFile bool
116118
}
117119

118120
// BuildOptions can be used to alter how an image is built.

docs/buildah-build.1.md

+5
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,11 @@ will convert /foo into a `shared` mount point. The propagation properties of th
12321232
mount can be changed directly. For instance if `/` is the source mount for
12331233
`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
12341234

1235+
**--with-ssl-cert-file**
1236+
1237+
If set, bind mount the host CA cert file (override location by setting `SSL_CERT_FILE`) within the RUN containers,
1238+
and set environment variable `SSL_CERT_FILE` to point to it.
1239+
12351240
## BUILD TIME VARIABLES
12361241

12371242
The ENV instruction in a Containerfile can be used to define variable values. When the image

docs/buildah-from.1.md

+5
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,11 @@ will convert /foo into a `shared` mount point. The propagation properties of th
690690
mount can be changed directly. For instance if `/` is the source mount for
691691
`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
692692

693+
**--with-ssl-cert-file**
694+
695+
If set, bind mount the host CA cert file (override location by setting `SSL_CERT_FILE`) within the RUN containers,
696+
and set environment variable `SSL_CERT_FILE` to point to it.
697+
693698
## EXAMPLE
694699

695700
buildah from --pull imagename

pkg/cli/common.go

+28-26
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,33 @@ type BudResults struct {
125125
// FromAndBugResults represents the results for common flags
126126
// in build and from
127127
type FromAndBudResults struct {
128-
AddHost []string
129-
BlobCache string
130-
CapAdd []string
131-
CapDrop []string
132-
CDIConfigDir string
133-
CgroupParent string
134-
CPUPeriod uint64
135-
CPUQuota int64
136-
CPUSetCPUs string
137-
CPUSetMems string
138-
CPUShares uint64
139-
DecryptionKeys []string
140-
Devices []string
141-
DNSSearch []string
142-
DNSServers []string
143-
DNSOptions []string
144-
HTTPProxy bool
145-
Isolation string
146-
Memory string
147-
MemorySwap string
148-
Retry int
149-
RetryDelay string
150-
SecurityOpt []string
151-
ShmSize string
152-
Ulimit []string
153-
Volumes []string
128+
AddHost []string
129+
BlobCache string
130+
CapAdd []string
131+
CapDrop []string
132+
CDIConfigDir string
133+
CgroupParent string
134+
CPUPeriod uint64
135+
CPUQuota int64
136+
CPUSetCPUs string
137+
CPUSetMems string
138+
CPUShares uint64
139+
DecryptionKeys []string
140+
Devices []string
141+
DNSSearch []string
142+
DNSServers []string
143+
DNSOptions []string
144+
HTTPProxy bool
145+
Isolation string
146+
Memory string
147+
MemorySwap string
148+
Retry int
149+
RetryDelay string
150+
SecurityOpt []string
151+
ShmSize string
152+
Ulimit []string
153+
Volumes []string
154+
WithSSLCertFile bool
154155
}
155156

156157
// GetUserNSFlags returns the common flags for usernamespace
@@ -409,6 +410,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults,
409410
fs.String("variant", "", "override the `variant` of the specified image")
410411
fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])")
411412
fs.StringVar(&flags.ShmSize, "shm-size", defaultContainerConfig.Containers.ShmSize, "size of '/dev/shm'. The format is `<number><unit>`.")
413+
fs.BoolVar(&flags.WithSSLCertFile, "with-ssl-cert-file", false, "mount host root CAs to /host-ssl-cert-file in container during build, and set SSL_CERT_FILE to point to it")
412414
fs.StringSliceVar(&flags.Ulimit, "ulimit", defaultContainerConfig.Containers.DefaultUlimits.Get(), "ulimit options")
413415
fs.StringArrayVarP(&flags.Volumes, "volume", "v", defaultContainerConfig.Volumes(), "bind mount a volume into the container")
414416

pkg/parse/parse.go

+25-23
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name
162162
cpuQuota, _ := flags.GetInt64("cpu-quota")
163163
cpuShares, _ := flags.GetUint64("cpu-shares")
164164
httpProxy, _ := flags.GetBool("http-proxy")
165+
withSSLCertFile, _ := flags.GetBool("with-ssl-cert-file")
165166
identityLabel, _ := flags.GetBool("identity-label")
166167
omitHistory, _ := flags.GetBool("omit-history")
167168

@@ -175,29 +176,30 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name
175176
ociHooks, _ := flags.GetStringArray("hooks-dir")
176177

177178
commonOpts := &define.CommonBuildOptions{
178-
AddHost: addHost,
179-
CPUPeriod: cpuPeriod,
180-
CPUQuota: cpuQuota,
181-
CPUSetCPUs: findFlagFunc("cpuset-cpus").Value.String(),
182-
CPUSetMems: findFlagFunc("cpuset-mems").Value.String(),
183-
CPUShares: cpuShares,
184-
CgroupParent: findFlagFunc("cgroup-parent").Value.String(),
185-
DNSOptions: dnsOptions,
186-
DNSSearch: dnsSearch,
187-
DNSServers: dnsServers,
188-
HTTPProxy: httpProxy,
189-
IdentityLabel: types.NewOptionalBool(identityLabel),
190-
Memory: memoryLimit,
191-
MemorySwap: memorySwap,
192-
NoHostname: noHostname,
193-
NoHosts: noHosts,
194-
OmitHistory: omitHistory,
195-
ShmSize: findFlagFunc("shm-size").Value.String(),
196-
Ulimit: ulimit,
197-
Volumes: volumes,
198-
Secrets: secrets,
199-
SSHSources: sshsources,
200-
OCIHooksDir: ociHooks,
179+
AddHost: addHost,
180+
CPUPeriod: cpuPeriod,
181+
CPUQuota: cpuQuota,
182+
CPUSetCPUs: findFlagFunc("cpuset-cpus").Value.String(),
183+
CPUSetMems: findFlagFunc("cpuset-mems").Value.String(),
184+
CPUShares: cpuShares,
185+
CgroupParent: findFlagFunc("cgroup-parent").Value.String(),
186+
DNSOptions: dnsOptions,
187+
DNSSearch: dnsSearch,
188+
DNSServers: dnsServers,
189+
HTTPProxy: httpProxy,
190+
IdentityLabel: types.NewOptionalBool(identityLabel),
191+
Memory: memoryLimit,
192+
MemorySwap: memorySwap,
193+
NoHostname: noHostname,
194+
NoHosts: noHosts,
195+
OmitHistory: omitHistory,
196+
ShmSize: findFlagFunc("shm-size").Value.String(),
197+
Ulimit: ulimit,
198+
Volumes: volumes,
199+
Secrets: secrets,
200+
SSHSources: sshsources,
201+
WithSSLCertFile: withSSLCertFile,
202+
OCIHooksDir: ociHooks,
201203
}
202204
securityOpts, _ := flags.GetStringArray("security-opt")
203205
if err := parseSecurityOpts(securityOpts, commonOpts); err != nil {

run_common.go

+4
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@ func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions
326326
}
327327
}
328328

329+
if b.CommonBuildOpts.WithSSLCertFile {
330+
g.AddProcessEnv("SSL_CERT_FILE", "/host-ssl-cert-file")
331+
}
332+
329333
for _, envSpec := range util.MergeEnv(util.MergeEnv(defaultEnv, b.Env()), options.Env) {
330334
env := strings.SplitN(envSpec, "=", 2)
331335
if len(env) > 1 {

run_linux.go

+8
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,14 @@ rootless=%d
503503
bindFiles["/run/.containerenv"] = containerenvPath
504504
}
505505

506+
if b.CommonBuildOpts.WithSSLCertFile {
507+
resolvedSSLCertPath, err := util.ResolveRootCACertFile()
508+
if err != nil {
509+
return err
510+
}
511+
bindFiles["/host-ssl-cert-file"] = resolvedSSLCertPath
512+
}
513+
506514
// Setup OCI hooks
507515
_, err = b.setupOCIHooks(spec, (len(options.Mounts) > 0 || len(volumes) > 0))
508516
if err != nil {

tests/bud.bats

+16
Original file line numberDiff line numberDiff line change
@@ -7383,3 +7383,19 @@ EOF
73837383
find ${TEST_SCRATCH_DIR}/buildcontext -ls
73847384
expect_output "" "build should not be able to write to build context"
73857385
}
7386+
7387+
@test "build-with-ssl-cert-file" {
7388+
echo "foofoofoofoo" > "${TEST_SCRATCH_DIR}/foocafile"
7389+
echo "barbarbarbar" > "${TEST_SCRATCH_DIR}/barcafile"
7390+
7391+
# use foocafile, and ensure that "foo" is output
7392+
SSL_CERT_FILE="${TEST_SCRATCH_DIR}/foocafile" run_buildah build -f <(echo 'FROM alpine'; echo 'RUN cat "${SSL_CERT_FILE}"') --tag="oci:${TEST_SCRATCH_DIR}/foodir" --timestamp 0 --with-ssl-cert-file
7393+
expect_output --substring "foofoofoofoo"
7394+
7395+
# use barcafile, and ensure that "bar" is output
7396+
SSL_CERT_FILE="${TEST_SCRATCH_DIR}/barcafile" run_buildah build -f <(echo 'FROM alpine'; echo 'RUN cat "${SSL_CERT_FILE}"') --tag="oci:${TEST_SCRATCH_DIR}/bardir" --timestamp 0 --with-ssl-cert-file
7397+
expect_output --substring "barbarbarbar"
7398+
7399+
# however regardless the produced images from both above should be identical as this is not persisted to image
7400+
diff -r "${TEST_SCRATCH_DIR}/foodir" "${TEST_SCRATCH_DIR}/bardir"
7401+
}

util/x509.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package util
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
)
8+
9+
func ResolveRootCACertFile() (string, error) {
10+
if rv, ok := os.LookupEnv("SSL_CERT_FILE"); ok {
11+
return rv, nil
12+
}
13+
for _, potFile := range certFiles {
14+
if _, err := os.Stat(potFile); err != nil {
15+
if os.IsNotExist(err) {
16+
continue
17+
}
18+
return "", fmt.Errorf("unexpected error resolving cert file: %w", err)
19+
}
20+
return potFile, nil
21+
}
22+
return "", errors.New("please set SSL_CERT_FILE to use --ssl-cert-file option")
23+
}

util/x509_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package util
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestResolveRootCACertFileWitEnvSet(t *testing.T) {
10+
t.Setenv("SSL_CERT_FILE", "/path/to/file")
11+
12+
path, err := ResolveRootCACertFile()
13+
assert.Nil(t, err)
14+
15+
assert.Equal(t, "/path/to/file", path)
16+
}

util/x509_unix.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//go:build !windows
2+
3+
package util
4+
5+
// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/crypto/x509/root_linux.go
6+
7+
/*
8+
Copyright 2009 The Go Authors.
9+
10+
Redistribution and use in source and binary forms, with or without
11+
modification, are permitted provided that the following conditions are
12+
met:
13+
14+
* Redistributions of source code must retain the above copyright
15+
notice, this list of conditions and the following disclaimer.
16+
* Redistributions in binary form must reproduce the above
17+
copyright notice, this list of conditions and the following disclaimer
18+
in the documentation and/or other materials provided with the
19+
distribution.
20+
* Neither the name of Google LLC nor the names of its
21+
contributors may be used to endorse or promote products derived from
22+
this software without specific prior written permission.
23+
24+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35+
*/
36+
37+
// Possible certificate files; stop after finding one.
38+
var certFiles = []string{
39+
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
40+
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
41+
"/etc/ssl/ca-bundle.pem", // OpenSUSE
42+
"/etc/pki/tls/cacert.pem", // OpenELEC
43+
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
44+
"/etc/ssl/cert.pem", // Alpine Linux
45+
}

util/x509_unix_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//go:build !windows
2+
3+
package util
4+
5+
import (
6+
"os"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestResolveRootCACertFileWithNoEnvSetUnix(t *testing.T) {
13+
// calling t.SetEnv has the side-effect of restoring it to the previous
14+
// value after the test (or leaving it unset).
15+
// We call this before unsetting it so that we benefit from that cleanup
16+
t.Setenv("SSL_CERT_FILE", "bogusval")
17+
18+
// now unset it (t.Unsetenv doesn't exist or we'd use that.)
19+
assert.Nil(t, os.Unsetenv("SSL_CERT_FILE"))
20+
path, err := ResolveRootCACertFile()
21+
assert.Nil(t, err)
22+
23+
// if our test env doesn't have any of the default locations set,
24+
// then we need to fix our lists
25+
assert.NotEmpty(t, path)
26+
}

util/x509_windows.go

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package util
2+
3+
// not implemented
4+
var certFiles []string

0 commit comments

Comments
 (0)