Skip to content

Commit 54b9e1f

Browse files
committed
Orchestrator pass CAs to sandbox trust store
1 parent 0f2ce48 commit 54b9e1f

File tree

11 files changed

+193
-8
lines changed

11 files changed

+193
-8
lines changed

packages/orchestrator/benchmark_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func BenchmarkBaseImageLaunch(b *testing.B) {
185185
b.Cleanup(templateCache.Stop)
186186

187187
sandboxes := sandbox.NewSandboxesMap()
188-
sandboxFactory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, featureFlags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), sandboxes)
188+
sandboxFactory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, featureFlags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), network.NewNoopEgressProxy(), sandboxes)
189189

190190
dockerhubRepository, err := dockerhub.GetRemoteRepository(b.Context())
191191
require.NoError(b, err)

packages/orchestrator/cmd/create-build/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func doBuild(
299299
defer templateCache.Stop()
300300

301301
buildMetrics, _ := metrics.NewBuildMetrics(noop.MeterProvider{})
302-
sandboxFactory := sandbox.NewFactory(c.BuilderConfig, networkPool, devicePool, featureFlags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), sandboxes)
302+
sandboxFactory := sandbox.NewFactory(c.BuilderConfig, networkPool, devicePool, featureFlags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), network.NewNoopEgressProxy(), sandboxes)
303303

304304
builder := build.NewBuilder(
305305
builderConfig, l, featureFlags, sandboxFactory,

packages/orchestrator/cmd/resume-build/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,7 @@ func run(ctx context.Context, buildID string, iterations int, coldStart, noPrefe
10521052
if verbose {
10531053
fmt.Println("🔧 Creating sandbox factory...")
10541054
}
1055-
factory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, flags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), sandboxes)
1055+
factory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, flags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), network.NewNoopEgressProxy(), sandboxes)
10561056

10571057
fmt.Printf("📦 Loading %s...\n", buildID)
10581058
tmpl, err := cache.GetTemplate(ctx, buildID, false, false)

packages/orchestrator/cmd/smoketest/smoke_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func newTestInfra(t *testing.T, ctx context.Context) *testInfra {
228228
ti.closers = append(ti.closers, func(ctx context.Context) { sandboxProxy.Close(ctx) })
229229

230230
// Factory + Builder
231-
factory := sandbox.NewFactory(orcConfig.BuilderConfig, networkPool, devicePool, flags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), sandboxes)
231+
factory := sandbox.NewFactory(orcConfig.BuilderConfig, networkPool, devicePool, flags, hoststats.NewNoopDelivery(), cgroup.NewNoopManager(), network.NewNoopEgressProxy(), sandboxes)
232232
ti.factory = factory
233233

234234
buildMetrics, _ := metrics.NewBuildMetrics(noop.MeterProvider{})

packages/orchestrator/pkg/factories/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func run(config cfg.Config, opts Options) (success bool) {
532532
closers = append(closers, closer{"network pool", networkPool.Close})
533533

534534
// sandbox factory
535-
sandboxFactory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, featureFlags, hostStatsDelivery, cgroupManager, sandboxes)
535+
sandboxFactory := sandbox.NewFactory(config.BuilderConfig, networkPool, devicePool, featureFlags, hostStatsDelivery, cgroupManager, egressSetup.Proxy, sandboxes)
536536

537537
// isolated filesystems cache (for nfs proxy)
538538
builder := chrooted.NewBuilder(config)

packages/orchestrator/pkg/sandbox/envd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.uber.org/zap"
1717

1818
"github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/envd"
19+
"github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/network"
1920
"github.com/e2b-dev/infra/packages/shared/pkg/consts"
2021
"github.com/e2b-dev/infra/packages/shared/pkg/logger"
2122
"github.com/e2b-dev/infra/packages/shared/pkg/telemetry"
@@ -42,6 +43,7 @@ func (s *Sandbox) doRequestWithInfiniteRetries(
4243
DefaultUser: utils.DerefOrDefault(s.Config.Envd.DefaultUser, ""),
4344
DefaultWorkdir: utils.DerefOrDefault(s.Config.Envd.DefaultWorkdir, ""),
4445
VolumeMounts: s.convertMounts(s.Config.VolumeMounts),
46+
CaCertificates: s.convertCACertificates(s.CACertificates),
4547
}
4648

4749
for {
@@ -95,6 +97,16 @@ func (s *Sandbox) convertMounts(mounts []VolumeMountConfig) []envd.VolumeMount {
9597
return results
9698
}
9799

100+
func (s *Sandbox) convertCACertificates(certs []network.CACertificate) []envd.CACertificate {
101+
results := make([]envd.CACertificate, 0, len(certs))
102+
103+
for _, cert := range certs {
104+
results = append(results, envd.CACertificate{Name: cert.Name, Cert: cert.Cert})
105+
}
106+
107+
return results
108+
}
109+
98110
func (s *Sandbox) initEnvd(ctx context.Context) (e error) {
99111
ctx, span := tracer.Start(ctx, "envd-init", trace.WithAttributes(telemetry.WithEnvdVersion(s.Config.Envd.Version)))
100112
defer func() {

packages/orchestrator/pkg/sandbox/envd/envd.gen.go

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package sandbox
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
"time"
10+
11+
"github.com/coreos/go-iptables/iptables"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
15+
"github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/envd"
16+
"github.com/e2b-dev/infra/packages/orchestrator/pkg/sandbox/network"
17+
)
18+
19+
// mockEgressProxy is a test EgressProxy that returns a fixed set of CA certificates.
20+
type mockEgressProxy struct {
21+
certs []network.CACertificate
22+
}
23+
24+
func (m *mockEgressProxy) OnSlotCreate(_ *network.Slot, _ *iptables.IPTables) error { return nil }
25+
func (m *mockEgressProxy) OnSlotDelete(_ *network.Slot, _ *iptables.IPTables) error { return nil }
26+
func (m *mockEgressProxy) CACertificates() []network.CACertificate { return m.certs }
27+
28+
// newTestSandboxWithCerts builds a minimal Sandbox that has CACertificates set —
29+
// mirroring what Factory.CreateSandbox does with f.egressProxy.CACertificates().
30+
func newTestSandboxWithCerts(certs []network.CACertificate) *Sandbox {
31+
return &Sandbox{
32+
Metadata: &Metadata{
33+
internalConfig: internalConfig{EnvdInitRequestTimeout: 5 * time.Second},
34+
Config: NewConfig(Config{}),
35+
Runtime: RuntimeMetadata{SandboxID: "test-sandbox"},
36+
},
37+
CACertificates: certs,
38+
}
39+
}
40+
41+
func TestConvertCACertificates(t *testing.T) {
42+
t.Parallel()
43+
44+
t.Run("converts network certs to envd certs preserving name and PEM", func(t *testing.T) {
45+
t.Parallel()
46+
47+
proxy := &mockEgressProxy{
48+
certs: []network.CACertificate{
49+
{Name: "proxy-ca", Cert: "-----BEGIN CERTIFICATE-----\nABC\n-----END CERTIFICATE-----\n"},
50+
{Name: "custom-ca", Cert: "-----BEGIN CERTIFICATE-----\nDEF\n-----END CERTIFICATE-----\n"},
51+
},
52+
}
53+
54+
sbx := newTestSandboxWithCerts(proxy.CACertificates())
55+
result := sbx.convertCACertificates(sbx.CACertificates)
56+
57+
require.Len(t, result, 2)
58+
assert.Equal(t, "proxy-ca", result[0].Name)
59+
assert.Equal(t, "-----BEGIN CERTIFICATE-----\nABC\n-----END CERTIFICATE-----\n", result[0].Cert)
60+
assert.Equal(t, "custom-ca", result[1].Name)
61+
assert.Equal(t, "-----BEGIN CERTIFICATE-----\nDEF\n-----END CERTIFICATE-----\n", result[1].Cert)
62+
})
63+
64+
t.Run("returns empty slice for nil certs", func(t *testing.T) {
65+
t.Parallel()
66+
sbx := newTestSandboxWithCerts(nil)
67+
result := sbx.convertCACertificates(nil)
68+
assert.Empty(t, result)
69+
})
70+
}
71+
72+
// TestEnvdInitSendsCACertificates verifies the full injection chain:
73+
// EgressProxy.CACertificates() → Sandbox.CACertificates → POST /init body.
74+
func TestEnvdInitSendsCACertificates(t *testing.T) {
75+
// Not parallel: overrides the package-level sandboxHttpClient.
76+
77+
proxy := &mockEgressProxy{
78+
certs: []network.CACertificate{
79+
{Name: "proxy-ca", Cert: "-----BEGIN CERTIFICATE-----\nPROXY\n-----END CERTIFICATE-----\n"},
80+
{Name: "custom-ca", Cert: "-----BEGIN CERTIFICATE-----\nCUSTOM\n-----END CERTIFICATE-----\n"},
81+
},
82+
}
83+
84+
// Simulate what Factory.CreateSandbox does when assigning egress proxy certs.
85+
sbx := newTestSandboxWithCerts(proxy.CACertificates())
86+
87+
var captured envd.PostInitJSONBody
88+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89+
require.Equal(t, http.MethodPost, r.Method)
90+
require.Equal(t, "/init", r.URL.Path)
91+
92+
err := json.NewDecoder(r.Body).Decode(&captured)
93+
require.NoError(t, err)
94+
95+
w.WriteHeader(http.StatusNoContent)
96+
}))
97+
defer server.Close()
98+
99+
// Temporarily swap the package-level client so the sandbox reaches our test server.
100+
orig := sandboxHttpClient
101+
sandboxHttpClient = http.Client{Timeout: 5 * time.Second}
102+
defer func() { sandboxHttpClient = orig }()
103+
104+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
105+
defer cancel()
106+
107+
_, _, err := sbx.doRequestWithInfiniteRetries(ctx, http.MethodPost, server.URL+"/init")
108+
require.NoError(t, err)
109+
110+
require.Len(t, captured.CaCertificates, 2)
111+
assert.Equal(t, proxy.certs[0].Name, captured.CaCertificates[0].Name)
112+
assert.Equal(t, proxy.certs[0].Cert, captured.CaCertificates[0].Cert)
113+
assert.Equal(t, proxy.certs[1].Name, captured.CaCertificates[1].Name)
114+
assert.Equal(t, proxy.certs[1].Cert, captured.CaCertificates[1].Cert)
115+
}

packages/orchestrator/pkg/sandbox/network/egressproxy.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,36 @@ import (
44
"github.com/coreos/go-iptables/iptables"
55
)
66

7+
type CACertificate struct {
8+
// Name is the filename (without extension) used when installing the cert into the trust store.
9+
Name string
10+
11+
// Cert is the PEM-encoded CA certificate.
12+
Cert string
13+
}
14+
715
type EgressProxy interface {
816
OnSlotCreate(s *Slot, tables *iptables.IPTables) error
917
OnSlotDelete(s *Slot, tables *iptables.IPTables) error
18+
19+
CACertificates() []CACertificate
1020
}
1121

1222
// NoopEgressProxy is a no-op implementation of EgressProxy.
1323
type NoopEgressProxy struct{}
1424

15-
func (NoopEgressProxy) OnSlotCreate(_ *Slot, _ *iptables.IPTables) error { return nil }
16-
func (NoopEgressProxy) OnSlotDelete(_ *Slot, _ *iptables.IPTables) error { return nil }
25+
func NewNoopEgressProxy() NoopEgressProxy {
26+
return NoopEgressProxy{}
27+
}
28+
29+
func (NoopEgressProxy) OnSlotCreate(_ *Slot, _ *iptables.IPTables) error {
30+
return nil
31+
}
32+
33+
func (NoopEgressProxy) OnSlotDelete(_ *Slot, _ *iptables.IPTables) error {
34+
return nil
35+
}
36+
37+
func (NoopEgressProxy) CACertificates() []CACertificate {
38+
return nil
39+
}

packages/orchestrator/pkg/sandbox/sandbox.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ type Sandbox struct {
229229
// It was used to store the config to allow API restarts
230230
APIStoredConfig *orchestrator.SandboxConfig
231231

232+
CACertificates []network.CACertificate
233+
232234
exit *utils.ErrorOnce
233235

234236
stop utils.Lazy[error]
@@ -273,6 +275,7 @@ type Factory struct {
273275
featureFlags *featureflags.Client
274276
hostStatsDelivery hoststats.Delivery
275277
cgroupManager cgroup.Manager
278+
egressProxy network.EgressProxy
276279
}
277280

278281
func NewFactory(
@@ -282,6 +285,7 @@ func NewFactory(
282285
featureFlags *featureflags.Client,
283286
hostStatsDelivery hoststats.Delivery,
284287
cgroupManager cgroup.Manager,
288+
egressProxy network.EgressProxy,
285289
sandboxes *Map,
286290
) *Factory {
287291
return &Factory{
@@ -292,6 +296,7 @@ func NewFactory(
292296
featureFlags: featureFlags,
293297
hostStatsDelivery: hostStatsDelivery,
294298
cgroupManager: cgroupManager,
299+
egressProxy: egressProxy,
295300
}
296301
}
297302

@@ -467,6 +472,8 @@ func (f *Factory) CreateSandbox(
467472

468473
APIStoredConfig: apiConfigToStore,
469474

475+
CACertificates: f.egressProxy.CACertificates(),
476+
470477
exit: exit,
471478
}
472479

@@ -805,6 +812,7 @@ func (f *Factory) ResumeSandbox(
805812
cleanup: cleanup,
806813

807814
APIStoredConfig: apiConfigToStore,
815+
CACertificates: f.egressProxy.CACertificates(),
808816

809817
exit: exit,
810818
}

0 commit comments

Comments
 (0)