Skip to content

Commit 1459d26

Browse files
authored
Merge branch 'main' into feat/fs-only-fsfreeze
2 parents e7834a0 + 247ac7a commit 1459d26

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package envd
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/e2b-dev/infra/tests/integration/internal/api"
13+
"github.com/e2b-dev/infra/tests/integration/internal/setup"
14+
"github.com/e2b-dev/infra/tests/integration/internal/utils"
15+
)
16+
17+
// bakedCertBundlePath is where the build packs the system trust store. On a
18+
// cold boot (filesystem-only reboot resume) envd.service seeds the /etc/ssl/certs
19+
// tmpfs from this tar and skips update-ca-certificates, so whatever is in here
20+
// is the trust store the sandbox comes up with.
21+
const bakedCertBundlePath = "/usr/local/share/e2b/ssl-certs.tar"
22+
23+
// injectCACommand writes a PEM into destPath as a shell command, WITHOUT running
24+
// update-ca-certificates — the realistic case (the /usr/local/share/ca-certificates
25+
// dir exists precisely so a dropped cert is picked up later). base64 avoids
26+
// shell-escaping the multi-line PEM.
27+
func injectCACommand(certPEM, destPath string) string {
28+
b64 := base64.StdEncoding.EncodeToString([]byte(certPEM))
29+
30+
return fmt.Sprintf("echo '%s' | base64 -d > %s", b64, destPath)
31+
}
32+
33+
// readBakedCertBundle extracts ca-certificates.crt from the build-time tar — i.e.
34+
// exactly what a cold boot would seed /etc/ssl/certs from.
35+
func readBakedCertBundle(t *testing.T, sbx *api.Sandbox, client *setup.EnvdClient) string {
36+
t.Helper()
37+
38+
out, err := utils.ExecCommandAsRootWithOutput(t, t.Context(), sbx, client,
39+
"tar", "-xOf", bakedCertBundlePath, "./ca-certificates.crt")
40+
require.NoError(t, err, "extract ca-certificates.crt from baked tar")
41+
42+
return out
43+
}
44+
45+
// TestCACertFromBuildSurvivesInBakedBundle verifies that a CA a template adds
46+
// during the build ends up in the cert tar packed at build time — the trust
47+
// store a cold-boot (filesystem-only reboot) resume comes up with.
48+
//
49+
// Because the boot fast path seeds /etc/ssl/certs from the tar and skips
50+
// update-ca-certificates, the tar must already contain every CA the template
51+
// produced. The finalize phase runs update-ca-certificates and packs the tar as
52+
// the build's last guest step (after all build steps and start/ready), so:
53+
// - a CA dropped under /usr/local/share/ca-certificates without the user
54+
// running update-ca-certificates must still be merged (RUN-step case);
55+
// - a CA installed by the start command — after configure.sh — must still be
56+
// captured (start-command case).
57+
//
58+
// This validates the tar *artifact* only. The boot-time seeding that consumes
59+
// it runs solely on a cold boot, which no runtime path triggers today — every
60+
// resume is a memory resume that restores the tmpfs from RAM and skips
61+
// ExecStartPre. Once filesystem-only reboot resume lands, the seeding becomes a
62+
// live path and should get its own end-to-end test (build a CA template, pause
63+
// memory:false, resume, assert the trust store). Keeping this guard now means
64+
// the artifact is already proven correct when that path turns on.
65+
func TestCACertFromBuildSurvivesInBakedBundle(t *testing.T) {
66+
t.Parallel()
67+
68+
tests := []struct {
69+
name string
70+
template string
71+
cn string
72+
buildData func(certPEM string) api.TemplateBuildStartV2
73+
}{
74+
{
75+
name: "added in RUN step without update-ca-certificates",
76+
template: "test-fs-only-ca-buildstep",
77+
cn: "e2b-test-ca-buildstep",
78+
buildData: func(certPEM string) api.TemplateBuildStartV2 {
79+
return api.TemplateBuildStartV2{
80+
FromImage: new("ubuntu:22.04"),
81+
Steps: new([]api.TemplateStep{
82+
{
83+
Type: "RUN",
84+
Force: new(true),
85+
// Run as root (RUN's optional 2nd arg): the CA dir is
86+
// root-owned and not world-writable until finalize, so
87+
// don't depend on the default build-step user.
88+
Args: new([]string{injectCACommand(certPEM, "/usr/local/share/ca-certificates/e2b-buildstep-ca.crt"), "root"}),
89+
},
90+
}),
91+
}
92+
},
93+
},
94+
{
95+
name: "installed by start command after configure",
96+
template: "test-fs-only-ca-startcmd",
97+
cn: "e2b-test-ca-startcmd",
98+
buildData: func(certPEM string) api.TemplateBuildStartV2 {
99+
return api.TemplateBuildStartV2{
100+
FromImage: new("ubuntu:22.04"),
101+
// Install the CA directly from the start command, which runs
102+
// after configure.sh (where /usr/local becomes world-writable),
103+
// then keep running. Exercises certs added after the
104+
// configuration script. Writing straight to the persisted CA dir
105+
// avoids staging through /tmp, which is tmpfs and would not
106+
// survive into the fresh finalize sandbox.
107+
StartCmd: new(injectCACommand(certPEM, "/usr/local/share/ca-certificates/e2b-startcmd-ca.crt") + " && sleep infinity"),
108+
ReadyCmd: new("for _ in $(seq 1 50); do test -f /usr/local/share/ca-certificates/e2b-startcmd-ca.crt && exit 0; sleep 0.2; done; exit 1"),
109+
}
110+
},
111+
},
112+
}
113+
114+
for _, tc := range tests {
115+
t.Run(tc.name, func(t *testing.T) {
116+
t.Parallel()
117+
118+
c := setup.GetAPIClient()
119+
client := setup.GetEnvdClient(t, t.Context())
120+
121+
certPEM := generateSelfSignedCA(t, tc.cn)
122+
123+
tmpl := utils.BuildTemplate(t, utils.TemplateBuildOptions{
124+
Name: tc.template,
125+
BuildData: tc.buildData(certPEM),
126+
LogHandler: utils.DefaultBuildLogHandler(t),
127+
ReqEditors: []api.RequestEditorFn{setup.WithAPIKey()},
128+
})
129+
130+
sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithTemplateID(tmpl.TemplateID))
131+
132+
bundle := readBakedCertBundle(t, sbx, client)
133+
assert.Contains(t, bundle, strings.TrimRight(certPEM, "\n"),
134+
"CA added during the build must be in the tar that seeds the cold-boot trust store")
135+
})
136+
}
137+
}

0 commit comments

Comments
 (0)