Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ IMAGE=kernel-docker ./build-docker.sh
IMAGE=kernel-docker ENABLE_WEBRTC=true ./run-docker.sh
```

### Outbound proxies

The images do not include an in-container Envoy proxy or its localhost certificate. Set `CHROMIUM_FLAGS` with `--proxy-server=<external-proxy-url>` to route Chromium through an external proxy, or omit the proxy flag for direct connections. Configure `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` separately for other tools that honor them.

Before upgrading older deployments, remove any dependency on `https://127.0.0.1:3128`, including fallback configurations and snapshot templates. Setting `XDS_SERVER` no longer starts a proxy. Instance identity fields remain available for telemetry and authenticated API requests.

## Running on a Unikernel

Alternatively, you can run the browser on a Unikraft unikernel.
Expand Down
16 changes: 0 additions & 16 deletions images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -417,16 +417,6 @@ COPY images/chromium-headful/supervisor/services/ /etc/supervisor/conf.d/service
COPY images/chromium-headful/openbox/rc.xml /etc/xdg/openbox/rc.xml
COPY images/chromium-headful/openbox/themes/kernel-light /usr/share/themes/kernel-light
COPY shared/set-desktop-background.sh /usr/local/bin/set-desktop-background.sh
COPY shared/envoy/supervisor-envoy.conf /etc/supervisor/conf.d/services/envoy.conf

# Install Envoy proxy
COPY shared/envoy/install-proxy.sh /usr/local/bin/install-proxy.sh
RUN chmod +x /usr/local/bin/install-proxy.sh && /usr/local/bin/install-proxy.sh && rm /usr/local/bin/install-proxy.sh

# Copy Envoy configuration files
COPY shared/envoy/bootstrap.yaml /etc/envoy/templates/bootstrap.yaml
COPY shared/envoy/init-envoy.sh /usr/local/bin/init-envoy.sh
RUN chmod +x /usr/local/bin/init-envoy.sh

# copy the kernel-images API binary built in the builder stage
COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api
Expand Down Expand Up @@ -464,10 +454,4 @@ RUN npm ci --ignore-scripts --no-audit --no-fund --omit=dev --prefix /tmp/browse

RUN useradd -m -s /bin/bash kernel

# Bake the envoy forward-proxy CA cert into the image (system trust store +
# NSS DB for both root and kernel users). See bake-certs.sh for the security
# rationale on sharing the cert across containers built from this image.
COPY shared/envoy/bake-certs.sh /usr/local/bin/bake-certs.sh
RUN chmod +x /usr/local/bin/bake-certs.sh && /usr/local/bin/bake-certs.sh && rm /usr/local/bin/bake-certs.sh

ENTRYPOINT [ "/wrapper" ]
16 changes: 0 additions & 16 deletions images/chromium-headless/image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -289,22 +289,6 @@ COPY --from=server-builder /out/wrapper /wrapper
# Supervisord configuration
COPY images/chromium-headless/image/supervisord.conf /etc/supervisor/supervisord.conf
COPY images/chromium-headless/image/supervisor/services/ /etc/supervisor/conf.d/services/
COPY shared/envoy/supervisor-envoy.conf /etc/supervisor/conf.d/services/envoy.conf

# Install Envoy proxy
COPY shared/envoy/install-proxy.sh /usr/local/bin/install-proxy.sh
RUN chmod +x /usr/local/bin/install-proxy.sh && /usr/local/bin/install-proxy.sh && rm /usr/local/bin/install-proxy.sh

# Copy Envoy configuration files
COPY shared/envoy/bootstrap.yaml /etc/envoy/templates/bootstrap.yaml
COPY shared/envoy/init-envoy.sh /usr/local/bin/init-envoy.sh
RUN chmod +x /usr/local/bin/init-envoy.sh

# Bake the envoy forward-proxy CA cert into the image (system trust store +
# NSS DB for both root and kernel users). See bake-certs.sh for the security
# rationale on sharing the cert across containers built from this image.
COPY shared/envoy/bake-certs.sh /usr/local/bin/bake-certs.sh
RUN chmod +x /usr/local/bin/bake-certs.sh && /usr/local/bin/bake-certs.sh && rm /usr/local/bin/bake-certs.sh

# Copy the kernel-images API binary built in the builder stage
COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api
Expand Down
2 changes: 1 addition & 1 deletion server/cmd/supervisord-shim/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestMapEventExitedUnexpectedFromStarting(t *testing.T) {
body, ok := mapEvent(
map[string]string{"eventname": "PROCESS_STATE_EXITED"},
map[string]string{
"processname": "envoy",
"processname": "dbus",
"from_state": "STARTING",
"expected": "0",
"pid": "55",
Expand Down
13 changes: 0 additions & 13 deletions server/cmd/wrapper/envoy.go

This file was deleted.

2 changes: 0 additions & 2 deletions server/cmd/wrapper/fork_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ func armForkIdentityWait(enabled bool) {
if !enabled {
return
}
stopAll("envoy")

for _, path := range []string{forkidentity.AppliedFile, forkidentity.PayloadFile} {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
fatalf("fork identity reset %s: %v", path, err)
Expand Down
6 changes: 2 additions & 4 deletions server/cmd/wrapper/fork_identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestForkIdentityAppliedMarkerRequiresEveryReadinessProbe(t *testing.T) {
forkidentity.AppliedFile = filepath.Join(dir, "fork-identity-applied")
t.Cleanup(func() { forkidentity.AppliedFile = oldAppliedFile })

probeNames := []string{"cdp", "chromedriver", "envoy"}
probeNames := []string{"cdp", "chromedriver", "neko"}
for _, failedProbe := range probeNames {
t.Run(failedProbe, func(t *testing.T) {
probes := make([]probe, 0, len(probeNames))
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestForkIdentityAppliedMarkerWrittenAfterReadiness(t *testing.T) {
_, allReady := waitProbesReady(time.Now(), []probe{
{name: "cdp", fn: func() bool { return true }},
{name: "chromedriver", fn: func() bool { return true }},
{name: "envoy", fn: func() bool { return true }},
{name: "neko", fn: func() bool { return true }},
}, time.Second)
require.True(t, allReady)
require.NoError(t, writeForkIdentityAppliedMarker("browser-1", allReady))
Expand All @@ -102,7 +102,6 @@ func TestApplyForkIdentityPayloadSetsAndClearsEnv(t *testing.T) {
err := applyForkIdentityPayload(forkidentity.Payload{
"instance_name": "browser-1",
"metro_name": "iad",
"xds_server": "xds.example.test",
"kernel_instance_jwt": "jwt",
"metro_api_url": "https://metro.example.test/browser/kernel",
"session_intel_url": "https://intel.example.test",
Expand All @@ -114,7 +113,6 @@ func TestApplyForkIdentityPayloadSetsAndClearsEnv(t *testing.T) {
assert.Equal(t, "browser-1", os.Getenv("INSTANCE_NAME"))
assert.Equal(t, "browser-1", os.Getenv("INST_NAME"))
assert.Equal(t, "iad", os.Getenv("METRO_NAME"))
assert.Equal(t, "xds.example.test", os.Getenv("XDS_SERVER"))
assert.Equal(t, "jwt", os.Getenv("KERNEL_INSTANCE_JWT"))
assert.Equal(t, "https://metro.example.test/browser/kernel", os.Getenv("KERNEL_METRO_API_BASE_URL"))
assert.Equal(t, "https://intel.example.test", os.Getenv("SESSION_INTEL_URL"))
Expand Down
21 changes: 5 additions & 16 deletions server/cmd/wrapper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,8 @@ func main() {
}()
waitForSocket(supervisorSock, 10*time.Second)

// Browser phase: identity-free services. Chromium itself doesn't read
// any per-instance identity envs — it just needs the envoy CA cert
// (baked into the image at build time, see shared/envoy/bake-certs.sh)
// so it trusts the forward proxy on first start with no runtime cert
// work to wait on. chromium-launcher internally waits for the X server
// Browser phase: identity-free services.
// chromium-launcher internally waits for the X server
// and (headful) for the window manager before exec'ing chromium, so we
// start it in parallel with the X server to overlap chromium-launcher's
// preamble with display startup. chromedriver listens on 9225 immediately
Expand Down Expand Up @@ -224,13 +221,9 @@ func main() {
return
}

// Identity phase: render envoy bootstrap with INST_NAME/JWT/etc. In fork
// identity wait mode, kernel-images-api was started early and is not
// restarted here.
// In fork identity wait mode, kernel-images-api was started early and
// reads the applied identity payload without restarting.
identityStart := time.Now()
if isExecutable("/usr/local/bin/init-envoy.sh") {
runStreamFatal("envoy-init", "/usr/local/bin/init-envoy.sh")
}
if !forkIdentityWait {
restartAll("kernel-images-api")
}
Expand Down Expand Up @@ -276,7 +269,6 @@ func main() {
// - chromedriver : TCP on chromedriver's internal port 9225 (api on 9224 is bound
// when api itself is up, which CDP readiness already implies)
// - neko : TCP on neko's HTTP port (8080), only when ENABLE_WEBRTC=true
// - envoy : TCP on envoy's listener (3128), only when envoy is enabled
func waitAllReady(t0 time.Time, webrtc bool, timeout time.Duration) (map[string]time.Duration, bool) {
chromePort := os.Getenv("CHROME_PORT")
probes := []probe{
Expand All @@ -286,9 +278,6 @@ func waitAllReady(t0 time.Time, webrtc bool, timeout time.Duration) (map[string]
if webrtc {
probes = append(probes, probe{"neko", func() bool { return tcpOK("127.0.0.1", "8080") }})
}
if envoyEnabled() {
probes = append(probes, probe{"envoy", func() bool { return tcpOK("127.0.0.1", "3128") }})
}

return waitProbesReady(t0, probes, timeout)
}
Expand Down Expand Up @@ -352,7 +341,7 @@ type probe struct {
// order so log lines diff cleanly across runs. Probes that never succeeded are
// omitted (they'd already have logged a WARNING separately).
func formatProbeDurations(d map[string]time.Duration) string {
order := []string{"cdp", "chromedriver", "neko", "envoy"}
order := []string{"cdp", "chromedriver", "neko"}
parts := make([]string, 0, len(d))
for _, name := range order {
if v, ok := d[name]; ok {
Expand Down
83 changes: 2 additions & 81 deletions server/cmd/wrapper/snapshot_start_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,14 @@ import (
"context"
"errors"
"fmt"
"os"
"time"

"github.com/kernel/kernel-images/server/lib/cdpclient"
)

const (
snapshotStartPageURL = "https://start.duckduckgo.com/"
envoyBootstrapPath = "/etc/envoy/bootstrap.yaml"
)

func prepareSnapshotStartPage(ctx context.Context, internalPort string) (retErr error) {
seedEnvoyStarted := false
seedEnvoyCleaned := false
defer func() {
if !seedEnvoyCleaned {
retErr = errors.Join(retErr, cleanupSeedEnvoyIfStarted(seedEnvoyStarted))
}
}()

if envoyEnabled() {
if !isExecutable("/usr/local/bin/init-envoy.sh") {
return fmt.Errorf("envoy is enabled but init-envoy.sh is unavailable")
}
if err := runStream("envoy-init", "/usr/local/bin/init-envoy.sh"); err != nil {
return fmt.Errorf("start seed envoy: %w", err)
}
seedEnvoyStarted = true
if err := waitForTCP(ctx, "127.0.0.1", "3128", 25*time.Second); err != nil {
return err
}
}
const snapshotStartPageURL = "https://start.duckduckgo.com/"

func prepareSnapshotStartPage(ctx context.Context, internalPort string) error {
versionURL := "http://127.0.0.1:" + internalPort + "/json/version"
devtoolsURL, err := cdpclient.BrowserWebSocketURL(ctx, versionURL)
if err != nil {
Expand All @@ -46,11 +21,6 @@ func prepareSnapshotStartPage(ctx context.Context, internalPort string) (retErr
navErr := cdpclient.DispatchStartURLAndWait(navCtx, devtoolsURL, "chrome://newtab/", snapshotStartPageURL)
cancel()

cleanupErr := cleanupSeedEnvoyIfStarted(seedEnvoyStarted)
seedEnvoyCleaned = true
if cleanupErr != nil {
return cleanupErr
}
if navErr == nil {
logf("snapshot start page ready url=%s", snapshotStartPageURL)
return nil
Expand All @@ -67,52 +37,3 @@ func prepareSnapshotStartPage(ctx context.Context, internalPort string) (retErr
}
return nil
}

func cleanupSeedEnvoyIfStarted(started bool) error {
if !started {
return nil
}
return cleanupSeedEnvoy()
}

func cleanupSeedEnvoy() error {
return cleanupSeedEnvoyWith(
envoyBootstrapPath,
func() { stopAll("envoy") },
func() bool { return tcpOK("127.0.0.1", "3128") },
)
}

func cleanupSeedEnvoyWith(bootstrapPath string, stop func(), running func() bool) error {
stop()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if !running() {
if err := os.Remove(bootstrapPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("remove seed envoy bootstrap: %w", err)
}
return nil
}
time.Sleep(20 * time.Millisecond)
}
return fmt.Errorf("seed envoy did not stop")
}

func waitForTCP(ctx context.Context, host, port string, timeout time.Duration) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
for {
if tcpOK(host, port) {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return fmt.Errorf("%s:%s did not become ready after %s", host, port, timeout)
case <-ticker.C:
}
}
}
30 changes: 0 additions & 30 deletions server/cmd/wrapper/snapshot_start_page_test.go

This file was deleted.

13 changes: 0 additions & 13 deletions server/cmd/wrapper/supervisord.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ func startAll(progs ...string) {
supervisorctl("start", progs...)
}

func stopAll(progs ...string) {
supervisorctl("stop", progs...)
}

// restartAll is the start-or-stop+start variant. It's used for services
// that need to pick up refreshed envs cleanly. supervisorctl `restart` is
// a no-op stop on cold programs followed by a normal start.
Expand Down Expand Up @@ -117,15 +113,6 @@ func runStream(label, name string, args ...string) error {
return cmd.Run()
}

// runStreamFatal is runStream + fatalf on non-zero exit. Use for scripts the
// boot path cannot proceed without (init-envoy). The old wrapper.sh ran under
// `set -o errexit`, so these were already fatal there.
func runStreamFatal(label, name string, args ...string) {
if err := runStream(label, name, args...); err != nil {
fatalf("%s failed: %v", label, err)
}
}

type prefixWriter struct {
label string
w *os.File
Expand Down
Loading
Loading