From a3a3570b2f81769d83d241611b9504152e30ce35 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:56:46 +0000 Subject: [PATCH 01/19] Pin Chromium's proxy with managed policy on filtered sessions --- server/cmd/api/api/api.go | 5 + server/cmd/api/api/api_test.go | 7 +- server/cmd/api/api/egress_policy.go | 43 ++++- server/cmd/api/api/egress_policy_test.go | 28 ++- server/cmd/chromium-launcher/main.go | 11 ++ server/e2e/e2e_egress_proxy_pin_test.go | 162 ++++++++++++++++ server/lib/egresspolicy/pin.go | 98 ++++++++++ server/lib/egresspolicy/pin_test.go | 115 ++++++++++++ server/lib/oapi/oapi.go | 224 ++++++++++++----------- server/openapi.yaml | 8 + 10 files changed, 587 insertions(+), 114 deletions(-) create mode 100644 server/e2e/e2e_egress_proxy_pin_test.go create mode 100644 server/lib/egresspolicy/pin.go create mode 100644 server/lib/egresspolicy/pin_test.go diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index 9cde7723..6f8c6231 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -141,6 +141,10 @@ type ApiService struct { // session's egress allowlist. Shared with the CDP proxy, which reads it to // decide whether to refuse a context that would set its own proxy. egressPolicy *egresspolicy.State + // egressPin is the managed policy that holds Chromium on the egress proxy + // while the session is filtered. The launcher writes it; the egress policy + // handler restarts Chromium to get it written and removes it. + egressPin egresspolicy.Pin lifecycleCtx context.Context lifecycleCancel context.CancelFunc @@ -199,6 +203,7 @@ func New( otlpExport: otlpExport, s2Storage: s2Storage, egressPolicy: egressPolicy, + egressPin: egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}, webmcp: webmcpclient.NewManager(upstreamMgr), browserRepl: newBrowserReplManager(), lifecycleCtx: ctx, diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index 4300a454..c3b3c77f 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -408,7 +408,12 @@ func testEgressPolicy(t *testing.T) *egresspolicy.State { func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { t.Helper() ts, es := newTelemetrySession(t) - return New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0, nil, nil, testEgressPolicy(t)) + svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0, nil, nil, testEgressPolicy(t)) + if err != nil { + return nil, err + } + svc.egressPin = egresspolicy.Pin{Path: filepath.Join(t.TempDir(), "kernel-egress.json")} + return svc, nil } func TestApiService_PatchChromiumFlags(t *testing.T) { diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index 6a888818..d811e98b 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -2,6 +2,7 @@ package api import ( "context" + "errors" "github.com/kernel/kernel-images/server/lib/logger" oapi "github.com/kernel/kernel-images/server/lib/oapi" @@ -15,12 +16,20 @@ func (s *ApiService) GetNetworkEgressPolicy(_ context.Context, _ oapi.GetNetwork // PutNetworkEgressPolicy handles PUT /network/egress-policy. // Records whether the session's egress is restricted to an allowlist, which is // what the CDP proxy consults before forwarding a command that would put a -// browser context on its own proxy. +// browser context on its own proxy, and pins Chromium's proxy with managed +// policy so an extension cannot switch it to direct connections. func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNetworkEgressPolicyRequestObject) (oapi.PutNetworkEgressPolicyResponseObject, error) { log := logger.FromContext(ctx) if req.Body == nil { return oapi.PutNetworkEgressPolicy400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "missing body"}}, nil } + + // The pin is written by the launcher from the flags Chromium starts with, + // so applying it means a restart, which must not interleave with another + // configuration change. + s.chromiumConfigMu.Lock() + defer s.chromiumConfigMu.Unlock() + // A policy that was not persisted would be lost if this process restarted, // so the caller is told it did not apply rather than being left to assume // a refusal is in place. @@ -28,6 +37,38 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet log.Error("failed to apply egress policy", "err", err, "filtered", req.Body.Filtered) return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil } + if err := s.applyEgressPin(ctx, req.Body.Filtered); err != nil { + log.Error("failed to apply egress proxy pin", "err", err, "filtered", req.Body.Filtered) + return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil + } log.Info("egress policy applied", "filtered", req.Body.Filtered) return oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: req.Body.Filtered}, nil } + +// applyEgressPin brings Chromium's proxy pin in line with the policy. The +// caller must hold chromiumConfigMu. +// +// A missing pin is written by restarting Chromium rather than by waiting for +// it to reload its policy directory, so the pin is in force before the control +// plane hands the session to a client. Chromium does not need a restart for the +// pin to come off. +func (s *ApiService) applyEgressPin(ctx context.Context, filtered bool) error { + if !filtered { + return s.egressPin.Remove() + } + present, err := s.egressPin.Present() + if err != nil || present { + return err + } + if err := s.restartChromiumAndWait(ctx, "egress policy"); err != nil { + return err + } + present, err = s.egressPin.Present() + if err != nil { + return err + } + if !present { + return errors.New("chromium restarted without the egress proxy pin") + } + return nil +} diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index 17c2fe69..45d6423d 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -22,20 +22,46 @@ func TestGetNetworkEgressPolicyDefaultsToUnfiltered(t *testing.T) { // The control plane applies the policy at setup and again whenever an // allowlist is added to or removed from a running session, so the endpoint has // to carry a session both ways and tolerate being told the same thing twice. +// The pin is already in place, as the launcher leaves it once Chromium has +// started filtered, so nothing here needs a restart. func TestPutNetworkEgressPolicy(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) + require.NoError(t, svc.egressPin.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"})) - for _, want := range []bool{true, true, false} { + for _, want := range []bool{true, true, false, false} { resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ Body: &oapi.NetworkEgressPolicy{Filtered: want}, }) require.NoError(t, err) require.Equal(t, oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: want}, resp) require.Equal(t, want, svc.egressPolicy.Filtered()) + pinned, err := svc.egressPin.Present() + require.NoError(t, err) + require.Equal(t, want, pinned) } } +// Without the pin, filtering means restarting Chromium so the launcher writes +// it. When that fails the caller is told so, and the session stays filtered: +// the next Chromium start pins the proxy, and the CDP proxy keeps refusing +// contexts with their own proxy in the meantime. +func TestPutNetworkEgressPolicyFailsWhenThePinCannotBeApplied(t *testing.T) { + svc, err := newSvc(t, newMockRecordManager()) + require.NoError(t, err) + t.Setenv("PATH", t.TempDir()) + + resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ + Body: &oapi.NetworkEgressPolicy{Filtered: true}, + }) + require.NoError(t, err) + require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp) + require.True(t, svc.egressPolicy.Filtered()) + pinned, err := svc.egressPin.Present() + require.NoError(t, err) + require.False(t, pinned) +} + func TestPutNetworkEgressPolicyRejectsMissingBody(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index d08d040b..a389408a 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "log/slog" "net" "os" "os/exec" @@ -12,6 +13,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/chromiumflags" + "github.com/kernel/kernel-images/server/lib/egresspolicy" "github.com/kernel/kernel-images/server/lib/x11" ) @@ -84,6 +86,15 @@ func main() { final = chromiumflags.TranslateKernelDisableFeatures(final) final = withDefaultPrivateNetworkBypass(final) + // A filtered session must not start without its proxy pinned: an extension + // could otherwise switch Chromium to direct connections around the egress + // proxy and the allowlist it enforces. + egress := egresspolicy.Load(egresspolicy.DefaultStatePath, slog.New(slog.NewTextHandler(os.Stderr, nil))) + if err := (egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}).Sync(egress.Filtered(), final); err != nil { + fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) + os.Exit(1) + } + // Diagnostics for parity with previous scripts fmt.Printf("BASE_FLAGS: %s\n", baseFlags) fmt.Printf("RUNTIME_FLAGS: %s\n", strings.Join(runtimeTokens, " ")) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go new file mode 100644 index 00000000..f44d692a --- /dev/null +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -0,0 +1,162 @@ +package e2e + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os/exec" + "testing" + "time" + + "github.com/kernel/kernel-images/server/lib/cdpclient" + instanceoapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/stretchr/testify/require" +) + +const egressPinPath = "/etc/chromium/policies/managed/kernel-egress.json" + +// TestEgressProxyPin checks that a filtered session pins Chromium's proxy with +// managed policy, so an extension loaded over CDP cannot take control of it, +// and that the pin comes off again when the session is unfiltered. +func TestEgressProxyPin(t *testing.T) { + t.Parallel() + + if _, err := exec.LookPath("docker"); err != nil { + require.NoError(t, err, "docker not available: %v", err) + } + + c := NewTestContainer(t, headlessImage) + ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) + defer cancel() + + require.NoError(t, c.Start(ctx, ContainerConfig{}), "failed to start container") + defer c.Stop(ctx) + require.NoError(t, c.WaitReady(ctx), "api not ready") + require.NoError(t, c.WaitDevTools(ctx), "devtools not ready") + + client, err := c.APIClient() + require.NoError(t, err) + + // Proxy v3 browsers are launched with the egress proxy on the command line. + // Nothing listens on this one; the test never loads a page. + flagsRsp, err := client.PatchChromiumFlagsWithResponse(ctx, instanceoapi.PatchChromiumFlagsJSONRequestBody{ + Flags: []string{"--proxy-server=http://127.0.0.1:9"}, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, flagsRsp.StatusCode(), "patch flags: %s", string(flagsRsp.Body)) + + putEgressPolicy(t, ctx, client, true) + + out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egressPinPath}) + require.NoError(t, err, "read pin: %s", out) + var pin struct { + ProxySettings map[string]string `json:"ProxySettings"` + } + require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) + require.Equal(t, map[string]string{ + "ProxyMode": "fixed_servers", + "ProxyServer": "http://127.0.0.1:9", + "ProxyBypassList": "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7", + }, pin.ProxySettings) + + // Loaded after the restart that applied the pin: extensions loaded over CDP + // do not survive one. The connection stays open for the rest of the test + // because an attached DevTools session keeps the service worker alive. + cdp, err := cdpclient.Dial(ctx, c.CDPURL()) + require.NoError(t, err) + defer cdp.Close() + worker := attachProxyExtensionWorker(t, ctx, c, cdp) + level, err := proxyLevelOfControl(ctx, cdp, worker) + require.NoError(t, err) + require.Equal(t, "not_controllable", level, "an extension can take over the proxy of a filtered session") + + putEgressPolicy(t, ctx, client, false) + + _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egressPinPath}) + require.NoError(t, err, "pin still present after the session was unfiltered") + + // Chromium reloads its policy directory on its own, so the extension + // regains control without a restart. + require.Eventually(t, func() bool { + level, err := proxyLevelOfControl(ctx, cdp, worker) + return err == nil && level == "controllable_by_this_extension" + }, time.Minute, time.Second, "proxy still pinned after the session was unfiltered") +} + +func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, filtered bool) { + t.Helper() + rsp, err := client.PutNetworkEgressPolicyWithResponse(ctx, instanceoapi.PutNetworkEgressPolicyJSONRequestBody{Filtered: filtered}) + require.NoError(t, err) + require.Equal(t, http.StatusOK, rsp.StatusCode(), "put egress policy filtered=%t: %s", filtered, string(rsp.Body)) +} + +// attachProxyExtensionWorker loads an extension with the proxy permission the +// way a CDP client can, and returns a session attached to its service worker. +func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContainer, cdp *cdpclient.Client) string { + t.Helper() + const dir = "/tmp/egress-pin-ext" + manifest := `{"manifest_version":3,"name":"proxy","version":"1.0","permissions":["proxy"],"background":{"service_worker":"bg.js"}}` + script := fmt.Sprintf("mkdir -p %[1]s && printf '%%s' '%[2]s' > %[1]s/manifest.json && printf '' > %[1]s/bg.js && chmod -R a+rX %[1]s", dir, manifest) + out, err := execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", script}) + require.NoError(t, err, "write extension: %s", out) + + extensionID, err := cdp.LoadUnpackedExtension(ctx, dir) + require.NoError(t, err) + + workerURL := "chrome-extension://" + extensionID + "/bg.js" + var targetID string + require.Eventually(t, func() bool { + raw, err := cdp.Send(ctx, "Target.getTargets", nil, "") + if err != nil { + return false + } + var res struct { + TargetInfos []struct { + TargetID string `json:"targetId"` + Type string `json:"type"` + URL string `json:"url"` + } `json:"targetInfos"` + } + if json.Unmarshal(raw, &res) != nil { + return false + } + for _, info := range res.TargetInfos { + if info.Type == "service_worker" && info.URL == workerURL { + targetID = info.TargetID + return true + } + } + return false + }, 30*time.Second, 250*time.Millisecond, "extension service worker never started") + + raw, err := cdp.Send(ctx, "Target.attachToTarget", map[string]any{"targetId": targetID, "flatten": true}, "") + require.NoError(t, err) + var attached struct { + SessionID string `json:"sessionId"` + } + require.NoError(t, json.Unmarshal(raw, &attached)) + return attached.SessionID +} + +// proxyLevelOfControl asks the extension's service worker whether it can set +// Chromium's proxy. +func proxyLevelOfControl(ctx context.Context, cdp *cdpclient.Client, worker string) (string, error) { + raw, err := cdp.Send(ctx, "Runtime.evaluate", map[string]any{ + "expression": `new Promise((resolve) => chrome.proxy.settings.get({}, (d) => resolve(d.levelOfControl)))`, + "awaitPromise": true, + "returnByValue": true, + }, worker) + if err != nil { + return "", err + } + var eval struct { + Result struct { + Value string `json:"value"` + } `json:"result"` + } + if err := json.Unmarshal(raw, &eval); err != nil { + return "", err + } + return eval.Result.Value, nil +} diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go new file mode 100644 index 00000000..1f9c135f --- /dev/null +++ b/server/lib/egresspolicy/pin.go @@ -0,0 +1,98 @@ +package egresspolicy + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strings" +) + +// DefaultPinPath is where the proxy pin is written. Chromium merges every file +// in its managed policy directory, so the pin sits beside policy.json rather +// than inside it and never races the writers of that file. +const DefaultPinPath = "/etc/chromium/policies/managed/kernel-egress.json" + +// Pin fixes Chromium's proxy with managed policy while the session is filtered. +// +// Chromium is pointed at the egress proxy with --proxy-server, but it applies +// proxy settings in the order policy, extensions, command line. An extension +// with the proxy permission can therefore switch it to direct connections, and +// a CDP client can load such an extension with Extensions.loadUnpacked. Policy +// is the one source an extension cannot override. +// +// The pin is derived from the flags Chromium is launched with, so it is written +// by the launcher on every start. Once it is in place Chromium ignores +// --proxy-bypass-list as well as --proxy-server, which is why it carries both. +type Pin struct { + Path string +} + +type proxySettings struct { + ProxyMode string `json:"ProxyMode"` + ProxyServer string `json:"ProxyServer"` + ProxyBypassList string `json:"ProxyBypassList,omitempty"` +} + +// Sync writes the pin for the given Chromium flags when the session is +// filtered, and removes it when it is not. +func (p Pin) Sync(filtered bool, flags []string) error { + if !filtered { + return p.Remove() + } + server, ok := lastFlagValue(flags, "--proxy-server") + if !ok || server == "" { + return errors.New("egress is filtered but Chromium has no --proxy-server to pin") + } + bypass, _ := lastFlagValue(flags, "--proxy-bypass-list") + data, err := json.Marshal(map[string]proxySettings{ + "ProxySettings": {ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, + }) + if err != nil { + return fmt.Errorf("encode proxy pin: %w", err) + } + tmp := p.Path + ".tmp" + if err := os.WriteFile(tmp, append(data, '\n'), 0o644); err != nil { + return fmt.Errorf("write proxy pin: %w", err) + } + if err := os.Rename(tmp, p.Path); err != nil { + return fmt.Errorf("replace proxy pin: %w", err) + } + return nil +} + +// Present reports whether the pin is in place. +func (p Pin) Present() (bool, error) { + _, err := os.Stat(p.Path) + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("stat proxy pin: %w", err) + } + return true, nil +} + +// Remove deletes the pin. Chromium reloads its policy directory on its own, so +// the proxy reverts to the command line without a restart. +func (p Pin) Remove() error { + if err := os.Remove(p.Path); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("remove proxy pin: %w", err) + } + return nil +} + +// lastFlagValue returns the value of the last occurrence of name, which is the +// one Chromium uses. A bare flag has an empty value. +func lastFlagValue(flags []string, name string) (string, bool) { + value, found := "", false + for _, flag := range flags { + switch { + case flag == name: + value, found = "", true + case strings.HasPrefix(flag, name+"="): + value, found = strings.TrimPrefix(flag, name+"="), true + } + } + return value, found +} diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go new file mode 100644 index 00000000..921e7c93 --- /dev/null +++ b/server/lib/egresspolicy/pin_test.go @@ -0,0 +1,115 @@ +package egresspolicy + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +func testPin(t *testing.T) Pin { + t.Helper() + return Pin{Path: filepath.Join(t.TempDir(), "kernel-egress.json")} +} + +func readPin(t *testing.T, p Pin) proxySettings { + t.Helper() + data, err := os.ReadFile(p.Path) + if err != nil { + t.Fatalf("read pin: %v", err) + } + var policy map[string]proxySettings + if err := json.Unmarshal(data, &policy); err != nil { + t.Fatalf("decode pin: %v", err) + } + if len(policy) != 1 { + t.Fatalf("pin sets %d policies, want only ProxySettings: %s", len(policy), data) + } + settings, ok := policy["ProxySettings"] + if !ok { + t.Fatalf("pin does not set ProxySettings: %s", data) + } + return settings +} + +// Once the pin is in place Chromium ignores the proxy flags entirely, so the +// pin has to reproduce what the last occurrence of each flag says. +func TestPinSyncFollowsChromiumFlags(t *testing.T) { + tests := []struct { + name string + flags []string + want proxySettings + }{ + { + name: "image default bypass", + flags: []string{"--kiosk", "--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, + }, + { + name: "runtime flags after base flags win", + flags: []string{"--proxy-server=http://old:1", "--proxy-bypass-list=10.0.0.0/8", "--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=preview.internal"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "preview.internal"}, + }, + { + name: "explicit empty bypass list", + flags: []string{"--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list="}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + }, + { + name: "bare empty bypass list", + flags: []string{"--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + }, + { + name: "no bypass list", + flags: []string{"--proxy-server=http://192.0.2.1:3129"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := testPin(t) + if err := p.Sync(true, tt.flags); err != nil { + t.Fatalf("Sync: %v", err) + } + if got := readPin(t, p); got != tt.want { + t.Fatalf("pin mismatch:\n got: %+v\nwant: %+v", got, tt.want) + } + }) + } +} + +// A filtered session without a proxy to pin would come up with nothing +// stopping an extension from going direct, so the launcher must not start it. +func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { + for _, flags := range [][]string{nil, {"--proxy-server="}, {"--proxy-server"}} { + p := testPin(t) + if err := p.Sync(true, flags); err == nil { + t.Fatalf("Sync(true, %q) succeeded", flags) + } + if present, _ := p.Present(); present { + t.Fatalf("Sync(true, %q) wrote a pin", flags) + } + } +} + +// Removing an allowlist hands the proxy back to the command line, so an +// extension the customer chose can set it again. +func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { + p := testPin(t) + if err := p.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { + t.Fatalf("Sync(true): %v", err) + } + for range 2 { + if err := p.Sync(false, nil); err != nil { + t.Fatalf("Sync(false): %v", err) + } + present, err := p.Present() + if err != nil { + t.Fatalf("Present: %v", err) + } + if present { + t.Fatal("pin still present after the session became unfiltered") + } + } +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 22194a0c..6dafde40 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -27232,117 +27232,119 @@ var swaggerSpec = []string{ "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", + "s6pzD68n7ViH5OPH//8AZGWvpYbWAwA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 94f66857..3cc0f5cc 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1752,6 +1752,14 @@ paths: answers it with a protocol error instead of forwarding it to Chromium. + With `filtered: true` the VM also pins Chromium's proxy with the + managed `ProxySettings` policy, so an extension with the `proxy` + permission cannot switch Chromium to direct connections. If the pin is + not already in place, Chromium is restarted to apply it before the + request returns. With `filtered: false` the pin is removed without a + restart. + + Idempotent, and applied by the control plane both when the session is set up and whenever an allowlist is added to or removed from a running session. The policy is persisted, so it survives a restart of the From 767dc7a666da6473a5fda6da66958ca32fb71871 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:22:40 +0000 Subject: [PATCH 02/19] Keep the egress pin's temp file out of the policy dir, sort it last, and pin WebRTC --- server/cmd/api/api/api_test.go | 6 +- server/cmd/api/api/egress_policy.go | 5 + server/cmd/api/api/egress_policy_test.go | 1 + server/e2e/e2e_egress_proxy_pin_test.go | 51 ++++-- server/lib/egresspolicy/pin.go | 26 ++- server/lib/egresspolicy/pin_test.go | 45 ++++- server/lib/oapi/oapi.go | 224 +++++++++++------------ server/openapi.yaml | 9 +- 8 files changed, 219 insertions(+), 148 deletions(-) diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index c3b3c77f..7d622d94 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -412,7 +412,11 @@ func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { if err != nil { return nil, err } - svc.egressPin = egresspolicy.Pin{Path: filepath.Join(t.TempDir(), "kernel-egress.json")} + managed := filepath.Join(t.TempDir(), "managed") + if err := os.Mkdir(managed, 0o755); err != nil { + return nil, err + } + svc.egressPin = egresspolicy.Pin{Path: filepath.Join(managed, "zz-kernel-egress.json")} return svc, nil } diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index d811e98b..3ce57731 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -52,6 +52,11 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet // it to reload its policy directory, so the pin is in force before the control // plane hands the session to a client. Chromium does not need a restart for the // pin to come off. +// +// The launcher does not take chromiumConfigMu, so a Chromium that crashed and is +// restarting as the policy flips to unfiltered can write the pin again after it +// is removed. That leaves the session pinned until Chromium next starts, which +// fails closed. func (s *ApiService) applyEgressPin(ctx context.Context, filtered bool) error { if !filtered { return s.egressPin.Remove() diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index 45d6423d..73282588 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -49,6 +49,7 @@ func TestPutNetworkEgressPolicy(t *testing.T) { func TestPutNetworkEgressPolicyFailsWhenThePinCannotBeApplied(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) + // Leaves supervisorctl off PATH, so the restart fails. t.Setenv("PATH", t.TempDir()) resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index f44d692a..bf2b08c6 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -10,15 +10,15 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/cdpclient" + "github.com/kernel/kernel-images/server/lib/egresspolicy" instanceoapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/require" ) -const egressPinPath = "/etc/chromium/policies/managed/kernel-egress.json" - -// TestEgressProxyPin checks that a filtered session pins Chromium's proxy with -// managed policy, so an extension loaded over CDP cannot take control of it, -// and that the pin comes off again when the session is unfiltered. +// TestEgressProxyPin checks that a filtered session pins Chromium's proxy and +// WebRTC IP handling with managed policy, so an extension loaded over CDP cannot +// take control of either, and that the pin comes off again when the session is +// unfiltered. func TestEgressProxyPin(t *testing.T) { t.Parallel() @@ -48,10 +48,11 @@ func TestEgressProxyPin(t *testing.T) { putEgressPolicy(t, ctx, client, true) - out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egressPinPath}) + out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPinPath}) require.NoError(t, err, "read pin: %s", out) var pin struct { - ProxySettings map[string]string `json:"ProxySettings"` + ProxySettings map[string]string `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` } require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) require.Equal(t, map[string]string{ @@ -59,6 +60,7 @@ func TestEgressProxyPin(t *testing.T) { "ProxyServer": "http://127.0.0.1:9", "ProxyBypassList": "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7", }, pin.ProxySettings) + require.Equal(t, "disable_non_proxied_udp", pin.WebRtcIPHandling) // Loaded after the restart that applied the pin: extensions loaded over CDP // do not survive one. The connection stays open for the rest of the test @@ -67,20 +69,27 @@ func TestEgressProxyPin(t *testing.T) { require.NoError(t, err) defer cdp.Close() worker := attachProxyExtensionWorker(t, ctx, c, cdp) - level, err := proxyLevelOfControl(ctx, cdp, worker) + level, err := levelOfControl(ctx, cdp, worker, proxySetting) require.NoError(t, err) require.Equal(t, "not_controllable", level, "an extension can take over the proxy of a filtered session") + level, err = levelOfControl(ctx, cdp, worker, webRTCSetting) + require.NoError(t, err) + require.Equal(t, "not_controllable", level, "an extension can let WebRTC send UDP around the proxy of a filtered session") putEgressPolicy(t, ctx, client, false) - _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egressPinPath}) + _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egresspolicy.DefaultPinPath}) require.NoError(t, err, "pin still present after the session was unfiltered") // Chromium reloads its policy directory on its own, so the extension // regains control without a restart. require.Eventually(t, func() bool { - level, err := proxyLevelOfControl(ctx, cdp, worker) - return err == nil && level == "controllable_by_this_extension" + proxy, err := levelOfControl(ctx, cdp, worker, proxySetting) + if err != nil { + return false + } + webRTC, err := levelOfControl(ctx, cdp, worker, webRTCSetting) + return err == nil && proxy == "controllable_by_this_extension" && webRTC == "controllable_by_this_extension" }, time.Minute, time.Second, "proxy still pinned after the session was unfiltered") } @@ -91,12 +100,12 @@ func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.Cli require.Equal(t, http.StatusOK, rsp.StatusCode(), "put egress policy filtered=%t: %s", filtered, string(rsp.Body)) } -// attachProxyExtensionWorker loads an extension with the proxy permission the -// way a CDP client can, and returns a session attached to its service worker. +// attachProxyExtensionWorker loads an extension with the proxy and privacy +// permissions the way a CDP client can, and returns a session attached to its service worker. func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContainer, cdp *cdpclient.Client) string { t.Helper() const dir = "/tmp/egress-pin-ext" - manifest := `{"manifest_version":3,"name":"proxy","version":"1.0","permissions":["proxy"],"background":{"service_worker":"bg.js"}}` + manifest := `{"manifest_version":3,"name":"proxy","version":"1.0","permissions":["proxy","privacy"],"background":{"service_worker":"bg.js"}}` script := fmt.Sprintf("mkdir -p %[1]s && printf '%%s' '%[2]s' > %[1]s/manifest.json && printf '' > %[1]s/bg.js && chmod -R a+rX %[1]s", dir, manifest) out, err := execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", script}) require.NoError(t, err, "write extension: %s", out) @@ -139,11 +148,17 @@ func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContai return attached.SessionID } -// proxyLevelOfControl asks the extension's service worker whether it can set -// Chromium's proxy. -func proxyLevelOfControl(ctx context.Context, cdp *cdpclient.Client, worker string) (string, error) { +// The extension settings the pin takes away from extensions. +const ( + proxySetting = "chrome.proxy.settings" + webRTCSetting = "chrome.privacy.network.webRTCIPHandlingPolicy" +) + +// levelOfControl asks the extension's service worker whether it can change a +// Chromium setting. +func levelOfControl(ctx context.Context, cdp *cdpclient.Client, worker, setting string) (string, error) { raw, err := cdp.Send(ctx, "Runtime.evaluate", map[string]any{ - "expression": `new Promise((resolve) => chrome.proxy.settings.get({}, (d) => resolve(d.levelOfControl)))`, + "expression": `new Promise((resolve) => ` + setting + `.get({}, (d) => resolve(d.levelOfControl)))`, "awaitPromise": true, "returnByValue": true, }, worker) diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 1f9c135f..5c61ed10 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" ) // DefaultPinPath is where the proxy pin is written. Chromium merges every file // in its managed policy directory, so the pin sits beside policy.json rather -// than inside it and never races the writers of that file. -const DefaultPinPath = "/etc/chromium/policies/managed/kernel-egress.json" +// than inside it and never races the writers of that file. Where two files set +// the same policy, the one that sorts last wins, so the name keeps the pin +// ahead of policy.json. +const DefaultPinPath = "/etc/chromium/policies/managed/zz-kernel-egress.json" // Pin fixes Chromium's proxy with managed policy while the session is filtered. // @@ -24,10 +27,19 @@ const DefaultPinPath = "/etc/chromium/policies/managed/kernel-egress.json" // The pin is derived from the flags Chromium is launched with, so it is written // by the launcher on every start. Once it is in place Chromium ignores // --proxy-bypass-list as well as --proxy-server, which is why it carries both. +// +// WebRTC sends UDP straight to the network rather than through an HTTP proxy, +// so a page can reach any STUN or TURN server whatever the proxy says. The pin +// also restricts WebRTC to connections that go through the proxy. type Pin struct { Path string } +type pinPolicy struct { + ProxySettings proxySettings `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` +} + type proxySettings struct { ProxyMode string `json:"ProxyMode"` ProxyServer string `json:"ProxyServer"` @@ -45,13 +57,17 @@ func (p Pin) Sync(filtered bool, flags []string) error { return errors.New("egress is filtered but Chromium has no --proxy-server to pin") } bypass, _ := lastFlagValue(flags, "--proxy-bypass-list") - data, err := json.Marshal(map[string]proxySettings{ - "ProxySettings": {ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, + data, err := json.Marshal(pinPolicy{ + ProxySettings: proxySettings{ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, + WebRtcIPHandling: "disable_non_proxied_udp", }) if err != nil { return fmt.Errorf("encode proxy pin: %w", err) } - tmp := p.Path + ".tmp" + // Staged outside the policy directory: Chromium reads every file in it, + // whatever the name, so a temporary file left there would pin the proxy + // where Present and Remove cannot see it. + tmp := filepath.Join(filepath.Dir(filepath.Dir(p.Path)), filepath.Base(p.Path)+".tmp") if err := os.WriteFile(tmp, append(data, '\n'), 0o644); err != nil { return fmt.Errorf("write proxy pin: %w", err) } diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index 921e7c93..41dc8303 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -7,9 +7,15 @@ import ( "testing" ) +// testPin lays out a policy directory the way the image does, so the pin's +// temporary file lands where it would on a real VM. func testPin(t *testing.T) Pin { t.Helper() - return Pin{Path: filepath.Join(t.TempDir(), "kernel-egress.json")} + managed := filepath.Join(t.TempDir(), "managed") + if err := os.Mkdir(managed, 0o755); err != nil { + t.Fatalf("create policy dir: %v", err) + } + return Pin{Path: filepath.Join(managed, "zz-kernel-egress.json")} } func readPin(t *testing.T, p Pin) proxySettings { @@ -18,18 +24,21 @@ func readPin(t *testing.T, p Pin) proxySettings { if err != nil { t.Fatalf("read pin: %v", err) } - var policy map[string]proxySettings + var policy map[string]json.RawMessage if err := json.Unmarshal(data, &policy); err != nil { t.Fatalf("decode pin: %v", err) } - if len(policy) != 1 { - t.Fatalf("pin sets %d policies, want only ProxySettings: %s", len(policy), data) + if len(policy) != 2 { + t.Fatalf("pin sets %d policies, want ProxySettings and WebRtcIPHandling: %s", len(policy), data) + } + var pinned pinPolicy + if err := json.Unmarshal(data, &pinned); err != nil { + t.Fatalf("decode pin: %v", err) } - settings, ok := policy["ProxySettings"] - if !ok { - t.Fatalf("pin does not set ProxySettings: %s", data) + if pinned.WebRtcIPHandling != "disable_non_proxied_udp" { + t.Fatalf("pin lets WebRTC send UDP around the proxy: %s", data) } - return settings + return pinned.ProxySettings } // Once the pin is in place Chromium ignores the proxy flags entirely, so the @@ -79,6 +88,26 @@ func TestPinSyncFollowsChromiumFlags(t *testing.T) { } } +// Chromium applies every file in the policy directory, whatever its name, so a +// write that fails before the rename must not leave its temporary file there. +func TestPinSyncFailureLeavesNothingInThePolicyDirectory(t *testing.T) { + p := testPin(t) + // A directory in the pin's place makes the rename fail after the write. + if err := os.MkdirAll(filepath.Join(p.Path, "blocker"), 0o755); err != nil { + t.Fatalf("block pin path: %v", err) + } + if err := p.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"}); err == nil { + t.Fatal("Sync succeeded over a directory") + } + entries, err := os.ReadDir(filepath.Dir(p.Path)) + if err != nil { + t.Fatalf("read policy dir: %v", err) + } + if len(entries) != 1 || entries[0].Name() != filepath.Base(p.Path) { + t.Fatalf("policy dir holds %v, want only the blocked pin path", entries) + } +} + // A filtered session without a proxy to pin would come up with nothing // stopping an extension from going direct, so the launcher must not start it. func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 6dafde40..8c2a175f 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -27233,118 +27233,118 @@ var swaggerSpec = []string{ "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", - "s6pzD68n7ViH5OPH//8AZGWvpYbWAwA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 3cc0f5cc..7bcf13a2 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1754,10 +1754,11 @@ paths: With `filtered: true` the VM also pins Chromium's proxy with the managed `ProxySettings` policy, so an extension with the `proxy` - permission cannot switch Chromium to direct connections. If the pin is - not already in place, Chromium is restarted to apply it before the - request returns. With `filtered: false` the pin is removed without a - restart. + permission cannot switch Chromium to direct connections, and with the + `WebRtcIPHandling` policy, so WebRTC cannot send UDP around the proxy. + If the pin is not already in place, Chromium is restarted to apply it + before the request returns. With `filtered: false` the pin is removed + without a restart. Idempotent, and applied by the control plane both when the session is From c82416c0bcba4f5720473169a4d5cec837cee0e6 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:51:10 +0000 Subject: [PATCH 03/19] Pin the base flags' egress proxy and clear per-URL WebRTC rules A runtime --proxy-server, which anyone holding the session's token can write, was pinned in place of the egress proxy, and a WebRtcIPHandlingUrl chrome policy was consulted before the pinned WebRtcIPHandling, turning direct UDP back on. The pin now takes the proxy server from the base flags only and sets WebRtcIPHandlingUrl to an empty list. --- server/cmd/api/api/egress_policy_test.go | 3 +- server/cmd/chromium-launcher/main.go | 3 +- server/e2e/e2e_egress_proxy_pin_test.go | 110 +++++++++-- server/lib/egresspolicy/pin.go | 34 ++-- server/lib/egresspolicy/pin_test.go | 58 ++++-- server/lib/oapi/oapi.go | 224 ++++++++++++----------- server/openapi.yaml | 20 +- 7 files changed, 287 insertions(+), 165 deletions(-) diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index 73282588..cfd0a107 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -27,7 +27,8 @@ func TestGetNetworkEgressPolicyDefaultsToUnfiltered(t *testing.T) { func TestPutNetworkEgressPolicy(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) - require.NoError(t, svc.egressPin.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"})) + flags := []string{"--proxy-server=http://192.0.2.1:3129"} + require.NoError(t, svc.egressPin.Sync(true, flags, flags)) for _, want := range []bool{true, true, false, false} { resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index a389408a..cc6e47bd 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -90,7 +90,8 @@ func main() { // could otherwise switch Chromium to direct connections around the egress // proxy and the allowlist it enforces. egress := egresspolicy.Load(egresspolicy.DefaultStatePath, slog.New(slog.NewTextHandler(os.Stderr, nil))) - if err := (egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}).Sync(egress.Filtered(), final); err != nil { + base := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, nil) + if err := (egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}).Sync(egress.Filtered(), base, final); err != nil { fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) os.Exit(1) } diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index bf2b08c6..44726e9f 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os/exec" + "strings" "testing" "time" @@ -16,21 +17,25 @@ import ( ) // TestEgressProxyPin checks that a filtered session pins Chromium's proxy and -// WebRTC IP handling with managed policy, so an extension loaded over CDP cannot -// take control of either, and that the pin comes off again when the session is -// unfiltered. +// WebRTC IP handling with managed policy, so that neither an extension loaded +// over CDP nor a runtime flag or chrome policy written through the instance API +// can take control of either, and that the pin comes off again when the session +// is unfiltered. func TestEgressProxyPin(t *testing.T) { t.Parallel() if _, err := exec.LookPath("docker"); err != nil { - require.NoError(t, err, "docker not available: %v", err) + t.Skipf("docker not available: %v", err) } c := NewTestContainer(t, headlessImage) ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) defer cancel() - require.NoError(t, c.Start(ctx, ContainerConfig{}), "failed to start container") + // Proxy v3 browsers are launched with the egress proxy in their base flags. + // Nothing listens on this one; the test only loads loopback pages, which + // Chromium never sends through a proxy. + require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{"CHROMIUM_FLAGS": "--proxy-server=http://127.0.0.1:9"}}), "failed to start container") defer c.Stop(ctx) require.NoError(t, c.WaitReady(ctx), "api not ready") require.NoError(t, c.WaitDevTools(ctx), "devtools not ready") @@ -38,21 +43,28 @@ func TestEgressProxyPin(t *testing.T) { client, err := c.APIClient() require.NoError(t, err) - // Proxy v3 browsers are launched with the egress proxy on the command line. - // Nothing listens on this one; the test never loads a page. + putEgressPolicy(t, ctx, client, true) + + // Anyone holding the session's token can write runtime flags and chrome + // policy. Neither may undo the pin: each write restarts Chromium, and the + // launcher pins the base flags' proxy and clears per-URL WebRTC rules. + policyRsp, err := client.PatchChromiumPoliciesWithResponse(ctx, instanceoapi.PatchChromiumPoliciesJSONRequestBody{ + "WebRtcIPHandlingUrl": []map[string]string{{"url": "*", "handling": "default"}}, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, policyRsp.StatusCode(), "patch policies: %s", string(policyRsp.Body)) flagsRsp, err := client.PatchChromiumFlagsWithResponse(ctx, instanceoapi.PatchChromiumFlagsJSONRequestBody{ - Flags: []string{"--proxy-server=http://127.0.0.1:9"}, + Flags: []string{"--proxy-server=http://127.0.0.1:8"}, }) require.NoError(t, err) require.Equal(t, http.StatusOK, flagsRsp.StatusCode(), "patch flags: %s", string(flagsRsp.Body)) - putEgressPolicy(t, ctx, client, true) - out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPinPath}) require.NoError(t, err, "read pin: %s", out) var pin struct { - ProxySettings map[string]string `json:"ProxySettings"` - WebRtcIPHandling string `json:"WebRtcIPHandling"` + ProxySettings map[string]string `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` + WebRtcIPHandlingURL []any `json:"WebRtcIPHandlingUrl"` } require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) require.Equal(t, map[string]string{ @@ -61,10 +73,12 @@ func TestEgressProxyPin(t *testing.T) { "ProxyBypassList": "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7", }, pin.ProxySettings) require.Equal(t, "disable_non_proxied_udp", pin.WebRtcIPHandling) + require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) + require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) - // Loaded after the restart that applied the pin: extensions loaded over CDP - // do not survive one. The connection stays open for the rest of the test - // because an attached DevTools session keeps the service worker alive. + // Loaded after the restarts above: extensions loaded over CDP do not + // survive one. The connection stays open for the rest of the test because + // an attached DevTools session keeps the service worker alive. cdp, err := cdpclient.Dial(ctx, c.CDPURL()) require.NoError(t, err) defer cdp.Close() @@ -75,6 +89,10 @@ func TestEgressProxyPin(t *testing.T) { level, err = levelOfControl(ctx, cdp, worker, webRTCSetting) require.NoError(t, err) require.Equal(t, "not_controllable", level, "an extension can let WebRTC send UDP around the proxy of a filtered session") + page := attachLoopbackPage(t, ctx, cdp) + udp, err := udpCandidates(ctx, cdp, page) + require.NoError(t, err) + require.Empty(t, udp, "WebRTC gathered UDP candidates on a filtered session") putEgressPolicy(t, ctx, client, false) @@ -91,6 +109,12 @@ func TestEgressProxyPin(t *testing.T) { webRTC, err := levelOfControl(ctx, cdp, worker, webRTCSetting) return err == nil && proxy == "controllable_by_this_extension" && webRTC == "controllable_by_this_extension" }, time.Minute, time.Second, "proxy still pinned after the session was unfiltered") + // The customer's per-URL rule applies again, which also shows the page can + // gather UDP candidates when nothing stops it. + require.Eventually(t, func() bool { + udp, err := udpCandidates(ctx, cdp, page) + return err == nil && len(udp) > 0 + }, time.Minute, time.Second, "per-URL WebRTC rule still cleared after the session was unfiltered") } func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, filtered bool) { @@ -148,6 +172,62 @@ func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContai return attached.SessionID } +// attachLoopbackPage opens a page on the DevTools HTTP endpoint, which needs no +// network and is never proxied, and returns a session attached to it. +func attachLoopbackPage(t *testing.T, ctx context.Context, cdp *cdpclient.Client) string { + t.Helper() + raw, err := cdp.Send(ctx, "Target.createTarget", map[string]any{"url": "http://127.0.0.1:9223/json/version"}, "") + require.NoError(t, err) + var created struct { + TargetID string `json:"targetId"` + } + require.NoError(t, json.Unmarshal(raw, &created)) + raw, err = cdp.Send(ctx, "Target.attachToTarget", map[string]any{"targetId": created.TargetID, "flatten": true}, "") + require.NoError(t, err) + var attached struct { + SessionID string `json:"sessionId"` + } + require.NoError(t, json.Unmarshal(raw, &attached)) + return attached.SessionID +} + +// udpCandidates gathers ICE candidates in the page and returns the UDP ones. No +// STUN server is configured: a host candidate is enough to show WebRTC may send +// UDP directly. +func udpCandidates(ctx context.Context, cdp *cdpclient.Client, page string) ([]string, error) { + raw, err := cdp.Send(ctx, "Runtime.evaluate", map[string]any{ + "expression": `new Promise((resolve) => { + const pc = new RTCPeerConnection(); + const candidates = []; + const done = () => { pc.close(); resolve(candidates); }; + pc.onicecandidate = (e) => e.candidate ? candidates.push(e.candidate.candidate) : done(); + pc.createDataChannel('probe'); + pc.createOffer().then((offer) => pc.setLocalDescription(offer)); + setTimeout(done, 5000); +})`, + "awaitPromise": true, + "returnByValue": true, + }, page) + if err != nil { + return nil, err + } + var eval struct { + Result struct { + Value []string `json:"value"` + } `json:"result"` + } + if err := json.Unmarshal(raw, &eval); err != nil { + return nil, err + } + var udp []string + for _, candidate := range eval.Result.Value { + if strings.Contains(strings.ToLower(candidate), " udp ") { + udp = append(udp, candidate) + } + } + return udp, nil +} + // The extension settings the pin takes away from extensions. const ( proxySetting = "chrome.proxy.settings" diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 5c61ed10..bb726900 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -30,14 +30,18 @@ const DefaultPinPath = "/etc/chromium/policies/managed/zz-kernel-egress.json" // // WebRTC sends UDP straight to the network rather than through an HTTP proxy, // so a page can reach any STUN or TURN server whatever the proxy says. The pin -// also restricts WebRTC to connections that go through the proxy. +// also restricts WebRTC to connections that go through the proxy, and clears +// the per-URL WebRtcIPHandlingUrl rules, which Chromium consults before +// WebRtcIPHandling and which a chrome_policy override could otherwise use to +// turn direct UDP back on. type Pin struct { Path string } type pinPolicy struct { - ProxySettings proxySettings `json:"ProxySettings"` - WebRtcIPHandling string `json:"WebRtcIPHandling"` + ProxySettings proxySettings `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` + WebRtcIPHandlingURL []struct{} `json:"WebRtcIPHandlingUrl"` } type proxySettings struct { @@ -46,20 +50,28 @@ type proxySettings struct { ProxyBypassList string `json:"ProxyBypassList,omitempty"` } -// Sync writes the pin for the given Chromium flags when the session is -// filtered, and removes it when it is not. -func (p Pin) Sync(filtered bool, flags []string) error { +// Sync writes the pin when the session is filtered, and removes it when it is +// not. +// +// The proxy server is taken from base, the flags the control plane launches +// the VM with, never from runtime flags: anyone holding the session's token +// can write those, and a runtime --proxy-server would otherwise be pinned in +// place of the egress proxy. The bypass list is taken from final, the flags +// Chromium is about to start with, because the control plane sets a session's +// private hosts at runtime. +func (p Pin) Sync(filtered bool, base, final []string) error { if !filtered { return p.Remove() } - server, ok := lastFlagValue(flags, "--proxy-server") + server, ok := lastFlagValue(base, "--proxy-server") if !ok || server == "" { - return errors.New("egress is filtered but Chromium has no --proxy-server to pin") + return errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") } - bypass, _ := lastFlagValue(flags, "--proxy-bypass-list") + bypass, _ := lastFlagValue(final, "--proxy-bypass-list") data, err := json.Marshal(pinPolicy{ - ProxySettings: proxySettings{ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, - WebRtcIPHandling: "disable_non_proxied_udp", + ProxySettings: proxySettings{ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, + WebRtcIPHandling: "disable_non_proxied_udp", + WebRtcIPHandlingURL: []struct{}{}, }) if err != nil { return fmt.Errorf("encode proxy pin: %w", err) diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index 41dc8303..10b9c5cb 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -28,8 +28,11 @@ func readPin(t *testing.T, p Pin) proxySettings { if err := json.Unmarshal(data, &policy); err != nil { t.Fatalf("decode pin: %v", err) } - if len(policy) != 2 { - t.Fatalf("pin sets %d policies, want ProxySettings and WebRtcIPHandling: %s", len(policy), data) + if len(policy) != 3 { + t.Fatalf("pin sets %d policies, want ProxySettings, WebRtcIPHandling and WebRtcIPHandlingUrl: %s", len(policy), data) + } + if string(policy["WebRtcIPHandlingUrl"]) != "[]" { + t.Fatalf("pin leaves per-URL WebRTC rules in place: %s", data) } var pinned pinPolicy if err := json.Unmarshal(data, &pinned); err != nil { @@ -42,43 +45,57 @@ func readPin(t *testing.T, p Pin) proxySettings { } // Once the pin is in place Chromium ignores the proxy flags entirely, so the -// pin has to reproduce what the last occurrence of each flag says. +// pin has to reproduce the egress proxy from the base flags and the bypass list +// Chromium starts with. func TestPinSyncFollowsChromiumFlags(t *testing.T) { + const proxy = "--proxy-server=http://192.0.2.1:3129" tests := []struct { name string - flags []string + base []string + final []string want proxySettings }{ { name: "image default bypass", - flags: []string{"--kiosk", "--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, + base: []string{"--kiosk", proxy}, + final: []string{"--kiosk", proxy, "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, }, { - name: "runtime flags after base flags win", - flags: []string{"--proxy-server=http://old:1", "--proxy-bypass-list=10.0.0.0/8", "--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=preview.internal"}, + name: "runtime bypass list after base flags wins", + base: []string{proxy, "--proxy-bypass-list=10.0.0.0/8"}, + final: []string{proxy, "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list=preview.internal"}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "preview.internal"}, }, + { + name: "runtime proxy server is not pinned", + base: []string{"--proxy-server=http://old:1", proxy}, + final: []string{"--proxy-server=http://old:1", proxy, "--proxy-server=direct://"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + }, { name: "explicit empty bypass list", - flags: []string{"--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list="}, + base: []string{proxy}, + final: []string{proxy, "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list="}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, { name: "bare empty bypass list", - flags: []string{"--proxy-server=http://192.0.2.1:3129", "--proxy-bypass-list"}, + base: []string{proxy}, + final: []string{proxy, "--proxy-bypass-list"}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, { name: "no bypass list", - flags: []string{"--proxy-server=http://192.0.2.1:3129"}, + base: []string{proxy}, + final: []string{proxy}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := testPin(t) - if err := p.Sync(true, tt.flags); err != nil { + if err := p.Sync(true, tt.base, tt.final); err != nil { t.Fatalf("Sync: %v", err) } if got := readPin(t, p); got != tt.want { @@ -96,7 +113,8 @@ func TestPinSyncFailureLeavesNothingInThePolicyDirectory(t *testing.T) { if err := os.MkdirAll(filepath.Join(p.Path, "blocker"), 0o755); err != nil { t.Fatalf("block pin path: %v", err) } - if err := p.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"}); err == nil { + flags := []string{"--proxy-server=http://192.0.2.1:3129"} + if err := p.Sync(true, flags, flags); err == nil { t.Fatal("Sync succeeded over a directory") } entries, err := os.ReadDir(filepath.Dir(p.Path)) @@ -110,14 +128,17 @@ func TestPinSyncFailureLeavesNothingInThePolicyDirectory(t *testing.T) { // A filtered session without a proxy to pin would come up with nothing // stopping an extension from going direct, so the launcher must not start it. +// A --proxy-server that only the runtime flags carry does not count: anyone +// holding the session's token can write those. func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { - for _, flags := range [][]string{nil, {"--proxy-server="}, {"--proxy-server"}} { + runtimeProxy := []string{"--proxy-server=http://192.0.2.1:3129"} + for _, base := range [][]string{nil, {"--proxy-server="}, {"--proxy-server"}} { p := testPin(t) - if err := p.Sync(true, flags); err == nil { - t.Fatalf("Sync(true, %q) succeeded", flags) + if err := p.Sync(true, base, runtimeProxy); err == nil { + t.Fatalf("Sync(true, %q) succeeded", base) } if present, _ := p.Present(); present { - t.Fatalf("Sync(true, %q) wrote a pin", flags) + t.Fatalf("Sync(true, %q) wrote a pin", base) } } } @@ -126,11 +147,12 @@ func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { // extension the customer chose can set it again. func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { p := testPin(t) - if err := p.Sync(true, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { + flags := []string{"--proxy-server=http://192.0.2.1:3129"} + if err := p.Sync(true, flags, flags); err != nil { t.Fatalf("Sync(true): %v", err) } for range 2 { - if err := p.Sync(false, nil); err != nil { + if err := p.Sync(false, nil, nil); err != nil { t.Fatalf("Sync(false): %v", err) } present, err := p.Present() diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 8c2a175f..26ff6509 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -27234,117 +27234,119 @@ var swaggerSpec = []string{ "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", + "3MPrSTvWIfn48f8/AONqs2vW1wMA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 7bcf13a2..e3bd1527 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1755,17 +1755,21 @@ paths: With `filtered: true` the VM also pins Chromium's proxy with the managed `ProxySettings` policy, so an extension with the `proxy` permission cannot switch Chromium to direct connections, and with the - `WebRtcIPHandling` policy, so WebRTC cannot send UDP around the proxy. - If the pin is not already in place, Chromium is restarted to apply it - before the request returns. With `filtered: false` the pin is removed - without a restart. + `WebRtcIPHandling` and `WebRtcIPHandlingUrl` policies, so WebRTC + cannot send UDP around the proxy. The pinned proxy server is the one + in Chromium's base flags; a `--proxy-server` added at runtime, for + example with `PATCH /chromium/flags`, does not replace it while the + session is filtered. If the pin is not already in place, Chromium is + restarted to apply it before the request returns, so filtering a + running session closes its open pages. With `filtered: false` the pin + is removed without a restart. Idempotent, and applied by the control plane both when the session is - set up and whenever an allowlist is added to or removed from a running - session. The policy is persisted, so it survives a restart of the - instance API process; a request that cannot persist it returns 500 - rather than reporting a policy that would be lost. + set up and whenever a running session's allowlist changes. The policy + is persisted, so it survives a restart of the instance API process; a + request that cannot persist it returns 500 rather than reporting a + policy that would be lost. operationId: putNetworkEgressPolicy x-telemetry-category: platform requestBody: From 1fb75aeb8c8d1cc0fc3a8b607999b8e06856a1aa Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:43:55 +0000 Subject: [PATCH 04/19] Wait for Chromium's policy watch before unpinning in the e2e test Chromium installs the watch on its managed policy directory in a best-effort task after it starts. A pin removed before then is found when the watch is installed, but the reload scheduled for it is replaced by the 15-minute periodic one, so under load the test could unfilter before the watch existed and time out. --- server/e2e/e2e_egress_proxy_pin_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index 44726e9f..b838fd01 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -94,6 +94,7 @@ func TestEgressProxyPin(t *testing.T) { require.NoError(t, err) require.Empty(t, udp, "WebRTC gathered UDP candidates on a filtered session") + waitForPolicyWatch(t, ctx, c) putEgressPolicy(t, ctx, client, false) _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egresspolicy.DefaultPinPath}) @@ -172,6 +173,23 @@ func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContai return attached.SessionID } +// waitForPolicyWatch waits until Chromium watches its managed policy +// directory. It installs the watch in a best-effort task some time after it +// starts, and a pin removed before then goes unnoticed until the periodic +// policy reload 15 minutes later: Chromium finds the change when it installs +// the watch, but the periodic reload it schedules next replaces the one it +// scheduled for the change. +func waitForPolicyWatch(t *testing.T, ctx context.Context, c *TestContainer) { + t.Helper() + const script = `ino=$(printf '%x' "$(stat -c %i /etc/chromium/policies/managed)") +browser=$(for p in $(pgrep -f -- --remote-debugging-port); do grep -qa -- --type= /proc/$p/cmdline || echo $p; done | sort -n | tail -n 1) +grep -qs "ino:$ino " /proc/$browser/fdinfo/*` + require.Eventually(t, func() bool { + _, err := execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", script}) + return err == nil + }, time.Minute, 500*time.Millisecond, "chromium never started watching its policy directory") +} + // attachLoopbackPage opens a page on the DevTools HTTP endpoint, which needs no // network and is never proxied, and returns a session attached to it. func attachLoopbackPage(t *testing.T, ctx context.Context, cdp *cdpclient.Client) string { From 9ebe374c0c4a395e16e191c5a4c110c52ff57b24 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:56:18 +0000 Subject: [PATCH 05/19] Wait for the extension's APIs before reading its settings in the e2e test The service worker target can be listed before its chrome.proxy and chrome.privacy bindings exist, so the first read could throw. levelOfControl now reports a thrown exception instead of failing to decode it. --- server/e2e/e2e_egress_proxy_pin_test.go | 33 +++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index b838fd01..009e4ffb 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -170,6 +170,23 @@ func attachProxyExtensionWorker(t *testing.T, ctx context.Context, c *TestContai SessionID string `json:"sessionId"` } require.NoError(t, json.Unmarshal(raw, &attached)) + + // The worker target can be listed before its extension APIs are bound. + require.Eventually(t, func() bool { + raw, err := cdp.Send(ctx, "Runtime.evaluate", map[string]any{ + "expression": `typeof ` + proxySetting + `?.get === "function" && typeof ` + webRTCSetting + `?.get === "function"`, + "returnByValue": true, + }, attached.SessionID) + if err != nil { + return false + } + var eval struct { + Result struct { + Value bool `json:"value"` + } `json:"result"` + } + return json.Unmarshal(raw, &eval) == nil && eval.Result.Value + }, 30*time.Second, 250*time.Millisecond, "extension APIs never became available in the service worker") return attached.SessionID } @@ -265,11 +282,23 @@ func levelOfControl(ctx context.Context, cdp *cdpclient.Client, worker, setting } var eval struct { Result struct { - Value string `json:"value"` + Value json.RawMessage `json:"value"` } `json:"result"` + ExceptionDetails *struct { + Exception struct { + Description string `json:"description"` + } `json:"exception"` + } `json:"exceptionDetails"` } if err := json.Unmarshal(raw, &eval); err != nil { return "", err } - return eval.Result.Value, nil + if eval.ExceptionDetails != nil { + return "", fmt.Errorf("read %s: %s", setting, eval.ExceptionDetails.Exception.Description) + } + var level string + if err := json.Unmarshal(eval.Result.Value, &level); err != nil { + return "", fmt.Errorf("read %s: %w", setting, err) + } + return level, nil } From 66a55a5cdf2a81704f852b4e8ab6d25083e34cea Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 19:57:42 +0000 Subject: [PATCH 06/19] Pin only private bypass entries and keep the policy directory root-owned The pin took --proxy-bypass-list from the runtime flags, which anyone holding the session's token can write, so a bypass list of "*" was pinned as policy and routed every request around the egress proxy. The pin now keeps only the entries the control plane accepts as private hosts and sends the rest through the proxy. The wrapper also chowned /etc/chromium/policies to the user Chromium runs as, though only the root API process writes there, which left the pin in a directory its own browser could write. The chown is gone, and the launcher makes the policy and staging directories root-owned and closed to others before writing the pin. Present now requires the file to be a pin, so one that is not still gets a restart that writes the real pin. The pin's staging directory is now explicit, and the temporary file is removed when a write fails. --- server/cmd/api/api/api.go | 2 +- server/cmd/api/api/api_test.go | 5 +- server/cmd/api/api/egress_policy_test.go | 3 +- server/cmd/chromium-launcher/main.go | 6 +- server/cmd/wrapper/system.go | 1 - server/e2e/e2e_egress_proxy_pin_test.go | 18 +- server/go.mod | 2 +- server/lib/egresspolicy/bypass.go | 183 ++++++++++++++++++ server/lib/egresspolicy/bypass_test.go | 75 ++++++++ server/lib/egresspolicy/pin.go | 106 ++++++++--- server/lib/egresspolicy/pin_test.go | 98 ++++++++-- server/lib/oapi/oapi.go | 227 ++++++++++++----------- server/openapi.yaml | 15 +- 13 files changed, 576 insertions(+), 165 deletions(-) create mode 100644 server/lib/egresspolicy/bypass.go create mode 100644 server/lib/egresspolicy/bypass_test.go diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index 6f8c6231..860daf67 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -203,7 +203,7 @@ func New( otlpExport: otlpExport, s2Storage: s2Storage, egressPolicy: egressPolicy, - egressPin: egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}, + egressPin: egresspolicy.DefaultPin, webmcp: webmcpclient.NewManager(upstreamMgr), browserRepl: newBrowserReplManager(), lifecycleCtx: ctx, diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index 7d622d94..8e7be05f 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -412,11 +412,12 @@ func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { if err != nil { return nil, err } - managed := filepath.Join(t.TempDir(), "managed") + policies := t.TempDir() + managed := filepath.Join(policies, "managed") if err := os.Mkdir(managed, 0o755); err != nil { return nil, err } - svc.egressPin = egresspolicy.Pin{Path: filepath.Join(managed, "zz-kernel-egress.json")} + svc.egressPin = egresspolicy.Pin{Path: filepath.Join(managed, "zz-kernel-egress.json"), StageDir: policies} return svc, nil } diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index cfd0a107..3e6ae245 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -28,7 +28,8 @@ func TestPutNetworkEgressPolicy(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) flags := []string{"--proxy-server=http://192.0.2.1:3129"} - require.NoError(t, svc.egressPin.Sync(true, flags, flags)) + _, err = svc.egressPin.Sync(true, flags, flags) + require.NoError(t, err) for _, want := range []bool{true, true, false, false} { resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index cc6e47bd..0d307ab1 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -91,7 +91,11 @@ func main() { // proxy and the allowlist it enforces. egress := egresspolicy.Load(egresspolicy.DefaultStatePath, slog.New(slog.NewTextHandler(os.Stderr, nil))) base := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, nil) - if err := (egresspolicy.Pin{Path: egresspolicy.DefaultPinPath}).Sync(egress.Filtered(), base, final); err != nil { + dropped, err := egresspolicy.DefaultPin.Sync(egress.Filtered(), base, final) + if len(dropped) > 0 { + fmt.Fprintf(os.Stderr, "egress proxy pin leaves out bypass entries that are not private hosts: %s\n", strings.Join(dropped, ";")) + } + if err != nil { fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) os.Exit(1) } diff --git a/server/cmd/wrapper/system.go b/server/cmd/wrapper/system.go index d030f96f..c98bd9a0 100644 --- a/server/cmd/wrapper/system.go +++ b/server/cmd/wrapper/system.go @@ -54,5 +54,4 @@ func prepareUserDirs(asRoot bool) { _ = exec.Command("chown", "-R", "kernel:kernel", "/home/kernel", "/home/kernel/user-data", "/home/kernel/.config", "/home/kernel/.pki", "/home/kernel/.cache").Run() - _ = exec.Command("chown", "-R", "kernel:kernel", "/etc/chromium/policies").Run() } diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index 009e4ffb..58d8020b 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -47,19 +47,20 @@ func TestEgressProxyPin(t *testing.T) { // Anyone holding the session's token can write runtime flags and chrome // policy. Neither may undo the pin: each write restarts Chromium, and the - // launcher pins the base flags' proxy and clears per-URL WebRTC rules. + // launcher pins the base flags' proxy, keeps only private hosts in the + // bypass list, and clears per-URL WebRTC rules. policyRsp, err := client.PatchChromiumPoliciesWithResponse(ctx, instanceoapi.PatchChromiumPoliciesJSONRequestBody{ "WebRtcIPHandlingUrl": []map[string]string{{"url": "*", "handling": "default"}}, }) require.NoError(t, err) require.Equal(t, http.StatusOK, policyRsp.StatusCode(), "patch policies: %s", string(policyRsp.Body)) flagsRsp, err := client.PatchChromiumFlagsWithResponse(ctx, instanceoapi.PatchChromiumFlagsJSONRequestBody{ - Flags: []string{"--proxy-server=http://127.0.0.1:8"}, + Flags: []string{"--proxy-server=http://127.0.0.1:8", "--proxy-bypass-list=*;example.com:8443;10.1.0.0/16"}, }) require.NoError(t, err) require.Equal(t, http.StatusOK, flagsRsp.StatusCode(), "patch flags: %s", string(flagsRsp.Body)) - out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPinPath}) + out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPin.Path}) require.NoError(t, err, "read pin: %s", out) var pin struct { ProxySettings map[string]string `json:"ProxySettings"` @@ -70,12 +71,19 @@ func TestEgressProxyPin(t *testing.T) { require.Equal(t, map[string]string{ "ProxyMode": "fixed_servers", "ProxyServer": "http://127.0.0.1:9", - "ProxyBypassList": "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7", + "ProxyBypassList": "example.com:8443;10.1.0.0/16", }, pin.ProxySettings) + require.Equal(t, "disable_non_proxied_udp", pin.WebRtcIPHandling) require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) + // The user Chromium runs as must not be able to add a policy file that + // sorts after the pin, or remove it. + out, err = execCombinedOutputWithClient(ctx, c, "stat", []string{"-c", "%U %a", "/etc/chromium/policies", "/etc/chromium/policies/managed"}) + require.NoError(t, err, "stat policy dirs: %s", out) + require.Equal(t, "root 755\nroot 755", strings.TrimSpace(out)) + // Loaded after the restarts above: extensions loaded over CDP do not // survive one. The connection stays open for the rest of the test because // an attached DevTools session keeps the service worker alive. @@ -97,7 +105,7 @@ func TestEgressProxyPin(t *testing.T) { waitForPolicyWatch(t, ctx, c) putEgressPolicy(t, ctx, client, false) - _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egresspolicy.DefaultPinPath}) + _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egresspolicy.DefaultPin.Path}) require.NoError(t, err, "pin still present after the session was unfiltered") // Chromium reloads its policy directory on its own, so the extension diff --git a/server/go.mod b/server/go.mod index 8d55fa81..0937513d 100644 --- a/server/go.mod +++ b/server/go.mod @@ -34,6 +34,7 @@ require ( go.opentelemetry.io/otel/sdk v1.45.0 go.opentelemetry.io/otel/sdk/log v0.20.0 go.opentelemetry.io/proto/otlp v1.11.0 + golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 golang.org/x/sys v0.47.0 golang.org/x/term v0.45.0 @@ -113,7 +114,6 @@ require ( go.opentelemetry.io/otel/trace v1.45.0 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.57.0 // indirect golang.org/x/text v0.40.0 // indirect golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect diff --git a/server/lib/egresspolicy/bypass.go b/server/lib/egresspolicy/bypass.go new file mode 100644 index 00000000..ebe628c8 --- /dev/null +++ b/server/lib/egresspolicy/bypass.go @@ -0,0 +1,183 @@ +package egresspolicy + +import ( + "net/netip" + "regexp" + "strconv" + "strings" + + "golang.org/x/net/publicsuffix" +) + +// The rules below mirror the control plane's validation of a session's +// private_hosts, which is the only legitimate source of a runtime +// --proxy-bypass-list. Anyone holding the session's token can write runtime +// flags too, and once a bypass list is pinned it routes traffic around the +// egress proxy, so the pin carries only entries the control plane would have +// accepted. + +const ( + maxBypassEntries = 32 + maxBypassEntryLen = 255 +) + +var ( + bypassEntryRegex = regexp.MustCompile(`^[A-Za-z0-9.*:/\[\]_-]+$`) + bypassLabelRegex = regexp.MustCompile(`^[A-Za-z0-9_](?:[A-Za-z0-9_-]{0,61}[A-Za-z0-9_])?$`) +) + +// privateRanges are the only ranges an IP or CIDR entry may cover: RFC1918, +// CGNAT, and IPv6 ULA. +var privateRanges = []netip.Prefix{ + netip.MustParsePrefix("10.0.0.0/8"), + netip.MustParsePrefix("172.16.0.0/12"), + netip.MustParsePrefix("192.168.0.0/16"), + netip.MustParsePrefix("100.64.0.0/10"), + netip.MustParsePrefix("fc00::/7"), +} + +// privateBypassList keeps the entries of a --proxy-bypass-list value that the +// control plane would accept as private hosts, and returns the rest as +// dropped. Dropping an entry sends its traffic through the egress proxy, so a +// rejected entry fails closed. +func privateBypassList(list string) (kept string, dropped []string) { + var entries []string + for _, entry := range strings.Split(list, ";") { + entry = strings.TrimSpace(entry) + if entry == "" { + continue + } + if len(entries) < maxBypassEntries && validBypassEntry(entry) { + entries = append(entries, entry) + } else { + dropped = append(dropped, entry) + } + } + return strings.Join(entries, ";"), dropped +} + +func validBypassEntry(entry string) bool { + if len(entry) > maxBypassEntryLen || !bypassEntryRegex.MatchString(entry) || strings.Contains(entry, "://") { + return false + } + if strings.Contains(entry, "/") { + prefix, err := netip.ParsePrefix(entry) + return err == nil && !prefix.Addr().Is4In6() && prefix == prefix.Masked() && prefixIsPrivate(prefix) + } + host, ok := hostWithoutPort(entry) + if !ok { + return false + } + if addr, err := netip.ParseAddr(host); err == nil { + return !addr.Is4In6() && addrIsPrivate(addr) + } + if isNumericIPv4(host) { + return false + } + if wildcards := strings.Count(host, "*"); wildcards > 0 { + if wildcards != 1 || !strings.HasPrefix(host, "*.") { + return false + } + host = strings.TrimPrefix(host, "*.") + if strings.Count(host, ".") < 1 || isPublicSuffix(host) { + return false + } + } + return validHostname(host) +} + +func prefixIsPrivate(prefix netip.Prefix) bool { + for _, r := range privateRanges { + if r.Bits() <= prefix.Bits() && r.Contains(prefix.Addr()) { + return true + } + } + return false +} + +func addrIsPrivate(addr netip.Addr) bool { + for _, r := range privateRanges { + if r.Contains(addr) { + return true + } + } + return false +} + +func hostWithoutPort(entry string) (string, bool) { + if strings.HasPrefix(entry, "[") { + end := strings.IndexByte(entry, ']') + if end < 0 { + return "", false + } + host := entry[1:end] + if addr, err := netip.ParseAddr(host); err != nil || !addr.Is6() { + return "", false + } + rest := entry[end+1:] + if rest == "" { + return host, true + } + if !strings.HasPrefix(rest, ":") || strings.ContainsAny(rest[1:], "[]:") { + return "", false + } + return host, validPort(rest[1:]) + } + if strings.ContainsAny(entry, "[]") { + return "", false + } + if addr, err := netip.ParseAddr(entry); err == nil { + // Chromium only matches an exact IPv6 address in bracketed form. + return entry, !addr.Is6() || addr.Is4In6() + } + if strings.Count(entry, ":") > 1 { + return "", false + } + if i := strings.LastIndexByte(entry, ':'); i >= 0 { + return entry[:i], validPort(entry[i+1:]) + } + return entry, true +} + +func validPort(value string) bool { + port, err := strconv.ParseUint(value, 10, 16) + return err == nil && port != 0 +} + +// isNumericIPv4 reports whether Chromium would parse host as an IPv4 address +// rather than a hostname, as it does with forms such as 0x7f.1. +func isNumericIPv4(host string) bool { + for _, part := range strings.Split(host, ".") { + if part == "" { + return false + } + digits := "0123456789" + if strings.HasPrefix(part, "0x") || strings.HasPrefix(part, "0X") { + part, digits = part[2:], "0123456789abcdefABCDEF" + } + for _, c := range part { + if !strings.ContainsRune(digits, c) { + return false + } + } + } + return true +} + +func isPublicSuffix(host string) bool { + host = strings.ToLower(host) + suffix, _ := publicsuffix.PublicSuffix(host) + return suffix == host +} + +func validHostname(host string) bool { + if host == "" || strings.HasPrefix(host, ".") || strings.HasSuffix(host, ".") || strings.Contains(host, "..") { + return false + } + for _, label := range strings.Split(host, ".") { + if !bypassLabelRegex.MatchString(label) { + return false + } + } + return true +} diff --git a/server/lib/egresspolicy/bypass_test.go b/server/lib/egresspolicy/bypass_test.go new file mode 100644 index 00000000..ee014e82 --- /dev/null +++ b/server/lib/egresspolicy/bypass_test.go @@ -0,0 +1,75 @@ +package egresspolicy + +import ( + "slices" + "testing" +) + +// Runtime flags can be written by anyone holding the session's token, so the +// pin keeps only the bypass entries the control plane accepts as private hosts. +func TestPrivateBypassList(t *testing.T) { + accepted := []string{ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.1.0/24", + "100.64.0.0/10", + "fc00::/7", + "fd00::/8", + "10.1.2.3", + "10.1.2.3:8080", + "[fd00::1]", + "[fd00::1]:8443", + "preview.internal", + "preview.internal:8443", + "*.corp.example.com", + "under_score.internal", + } + rejected := []string{ + "*", + "", + "<-loopback>", + "*.com", + "*.co.uk", + "*.internal", + "*example.com", + "a.*.example.com", + "8.8.8.8", + "8.8.8.0/24", + "0.0.0.0/0", + "10.0.0.1/8", + "127.0.0.1", + "169.254.169.254", + "fd00::1", + "::ffff:10.0.0.1", + "0x0a.1.2.3", + "http://preview.internal", + "preview.internal:0", + "preview.internal:65536", + "-bad.internal", + "bad..internal", + } + for _, entry := range accepted { + if kept, dropped := privateBypassList(entry); kept != entry || len(dropped) != 0 { + t.Errorf("privateBypassList(%q) = %q, %q; want it kept", entry, kept, dropped) + } + } + for _, entry := range rejected { + if kept, dropped := privateBypassList(entry); kept != "" || !slices.Equal(dropped, []string{entry}) { + t.Errorf("privateBypassList(%q) = %q, %q; want it dropped", entry, kept, dropped) + } + } +} + +func TestPrivateBypassListCapsEntries(t *testing.T) { + var list string + for i := range maxBypassEntries + 1 { + if i > 0 { + list += ";" + } + list += "10.0.0." + string(rune('0'+i%10)) + } + _, dropped := privateBypassList(list) + if len(dropped) != 1 { + t.Fatalf("dropped %d entries, want 1 past the cap of %d", len(dropped), maxBypassEntries) + } +} diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index bb726900..6793c697 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -7,14 +7,18 @@ import ( "os" "path/filepath" "strings" + "syscall" ) -// DefaultPinPath is where the proxy pin is written. Chromium merges every file +// DefaultPin is the pin as the image lays it out. Chromium merges every file // in its managed policy directory, so the pin sits beside policy.json rather // than inside it and never races the writers of that file. Where two files set // the same policy, the one that sorts last wins, so the name keeps the pin // ahead of policy.json. -const DefaultPinPath = "/etc/chromium/policies/managed/zz-kernel-egress.json" +var DefaultPin = Pin{ + Path: "/etc/chromium/policies/managed/zz-kernel-egress.json", + StageDir: "/etc/chromium/policies", +} // Pin fixes Chromium's proxy with managed policy while the session is filtered. // @@ -35,13 +39,18 @@ const DefaultPinPath = "/etc/chromium/policies/managed/zz-kernel-egress.json" // WebRtcIPHandling and which a chrome_policy override could otherwise use to // turn direct UDP back on. type Pin struct { + // Path is the pin's file in Chromium's managed policy directory. Path string + // StageDir is where the pin is written before it is renamed into place. + // It must be outside the policy directory, since Chromium applies every + // file there whatever its name, and on the same filesystem. + StageDir string } type pinPolicy struct { - ProxySettings proxySettings `json:"ProxySettings"` - WebRtcIPHandling string `json:"WebRtcIPHandling"` - WebRtcIPHandlingURL []struct{} `json:"WebRtcIPHandlingUrl"` + ProxySettings proxySettings `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` + WebRtcIPHandlingURL []json.RawMessage `json:"WebRtcIPHandlingUrl"` } type proxySettings struct { @@ -50,55 +59,81 @@ type proxySettings struct { ProxyBypassList string `json:"ProxyBypassList,omitempty"` } +const ( + pinProxyMode = "fixed_servers" + pinWebRtcIPHandling = "disable_non_proxied_udp" +) + // Sync writes the pin when the session is filtered, and removes it when it is -// not. +// not. It returns the bypass list entries it left out of the pin. // // The proxy server is taken from base, the flags the control plane launches // the VM with, never from runtime flags: anyone holding the session's token // can write those, and a runtime --proxy-server would otherwise be pinned in // place of the egress proxy. The bypass list is taken from final, the flags // Chromium is about to start with, because the control plane sets a session's -// private hosts at runtime. -func (p Pin) Sync(filtered bool, base, final []string) error { +// private hosts at runtime; for the same reason it keeps only entries the +// control plane would accept as private hosts. +// +// The policy directory, and the staging directory the pin is renamed from, +// are made the writer's own and closed to everyone else, so the user Chromium +// runs as cannot add a policy file that sorts after the pin or remove it. +func (p Pin) Sync(filtered bool, base, final []string) ([]string, error) { if !filtered { - return p.Remove() + return nil, p.Remove() } server, ok := lastFlagValue(base, "--proxy-server") if !ok || server == "" { - return errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") + return nil, errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") } - bypass, _ := lastFlagValue(final, "--proxy-bypass-list") + rawBypass, _ := lastFlagValue(final, "--proxy-bypass-list") + bypass, dropped := privateBypassList(rawBypass) data, err := json.Marshal(pinPolicy{ - ProxySettings: proxySettings{ProxyMode: "fixed_servers", ProxyServer: server, ProxyBypassList: bypass}, - WebRtcIPHandling: "disable_non_proxied_udp", - WebRtcIPHandlingURL: []struct{}{}, + ProxySettings: proxySettings{ProxyMode: pinProxyMode, ProxyServer: server, ProxyBypassList: bypass}, + WebRtcIPHandling: pinWebRtcIPHandling, + WebRtcIPHandlingURL: []json.RawMessage{}, }) if err != nil { - return fmt.Errorf("encode proxy pin: %w", err) + return dropped, fmt.Errorf("encode proxy pin: %w", err) + } + for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { + if err := ownDir(dir); err != nil { + return dropped, err + } } - // Staged outside the policy directory: Chromium reads every file in it, - // whatever the name, so a temporary file left there would pin the proxy - // where Present and Remove cannot see it. - tmp := filepath.Join(filepath.Dir(filepath.Dir(p.Path)), filepath.Base(p.Path)+".tmp") + tmp := filepath.Join(p.StageDir, filepath.Base(p.Path)+".tmp") + defer os.Remove(tmp) if err := os.WriteFile(tmp, append(data, '\n'), 0o644); err != nil { - return fmt.Errorf("write proxy pin: %w", err) + return dropped, fmt.Errorf("write proxy pin: %w", err) } if err := os.Rename(tmp, p.Path); err != nil { - return fmt.Errorf("replace proxy pin: %w", err) + return dropped, fmt.Errorf("replace proxy pin: %w", err) } - return nil + return dropped, nil } -// Present reports whether the pin is in place. +// Present reports whether a pin is in place. A file at the pin's path that is +// not a pin does not count, so the caller writes a real one over it. func (p Pin) Present() (bool, error) { - _, err := os.Stat(p.Path) + data, err := os.ReadFile(p.Path) if errors.Is(err, os.ErrNotExist) { return false, nil } if err != nil { - return false, fmt.Errorf("stat proxy pin: %w", err) + return false, fmt.Errorf("read proxy pin: %w", err) + } + var pinned struct { + ProxySettings proxySettings `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` + WebRtcIPHandlingURL *[]json.RawMessage `json:"WebRtcIPHandlingUrl"` + } + if json.Unmarshal(data, &pinned) != nil { + return false, nil } - return true, nil + return pinned.ProxySettings.ProxyMode == pinProxyMode && + pinned.ProxySettings.ProxyServer != "" && + pinned.WebRtcIPHandling == pinWebRtcIPHandling && + pinned.WebRtcIPHandlingURL != nil && len(*pinned.WebRtcIPHandlingURL) == 0, nil } // Remove deletes the pin. Chromium reloads its policy directory on its own, so @@ -110,6 +145,25 @@ func (p Pin) Remove() error { return nil } +// ownDir makes dir owned by the current user and writable by no one else. +func ownDir(dir string) error { + info, err := os.Stat(dir) + if err != nil { + return fmt.Errorf("stat %s: %w", dir, err) + } + if st, ok := info.Sys().(*syscall.Stat_t); ok && int(st.Uid) != os.Geteuid() { + if err := os.Chown(dir, os.Geteuid(), os.Getegid()); err != nil { + return fmt.Errorf("take ownership of %s: %w", dir, err) + } + } + if info.Mode().Perm()&0o022 != 0 { + if err := os.Chmod(dir, info.Mode().Perm()&^0o022); err != nil { + return fmt.Errorf("restrict %s: %w", dir, err) + } + } + return nil +} + // lastFlagValue returns the value of the last occurrence of name, which is the // one Chromium uses. A bare flag has an empty value. func lastFlagValue(flags []string, name string) (string, bool) { diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index 10b9c5cb..f59d51d9 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -7,15 +7,16 @@ import ( "testing" ) -// testPin lays out a policy directory the way the image does, so the pin's -// temporary file lands where it would on a real VM. +// testPin lays out a policy directory the way the image does, with the pin +// staged in its parent. func testPin(t *testing.T) Pin { t.Helper() - managed := filepath.Join(t.TempDir(), "managed") + policies := t.TempDir() + managed := filepath.Join(policies, "managed") if err := os.Mkdir(managed, 0o755); err != nil { t.Fatalf("create policy dir: %v", err) } - return Pin{Path: filepath.Join(managed, "zz-kernel-egress.json")} + return Pin{Path: filepath.Join(managed, "zz-kernel-egress.json"), StageDir: policies} } func readPin(t *testing.T, p Pin) proxySettings { @@ -67,6 +68,18 @@ func TestPinSyncFollowsChromiumFlags(t *testing.T) { final: []string{proxy, "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list=preview.internal"}, want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "preview.internal"}, }, + { + name: "bypass entries that are not private hosts are left out", + base: []string{proxy}, + final: []string{proxy, "--proxy-bypass-list=*;;*.com;8.8.8.0/24;10.1.0.0/16;preview.internal:8443"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.1.0.0/16;preview.internal:8443"}, + }, + { + name: "bypass list with nothing private pins no bypass", + base: []string{proxy}, + final: []string{proxy, "--proxy-bypass-list=*"}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + }, { name: "runtime proxy server is not pinned", base: []string{"--proxy-server=http://old:1", proxy}, @@ -95,7 +108,7 @@ func TestPinSyncFollowsChromiumFlags(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := testPin(t) - if err := p.Sync(true, tt.base, tt.final); err != nil { + if _, err := p.Sync(true, tt.base, tt.final); err != nil { t.Fatalf("Sync: %v", err) } if got := readPin(t, p); got != tt.want { @@ -106,15 +119,16 @@ func TestPinSyncFollowsChromiumFlags(t *testing.T) { } // Chromium applies every file in the policy directory, whatever its name, so a -// write that fails before the rename must not leave its temporary file there. -func TestPinSyncFailureLeavesNothingInThePolicyDirectory(t *testing.T) { +// write that fails before the rename must not leave its temporary file there, +// nor in the staging directory, from where a later rename could move it. +func TestPinSyncFailureLeavesNoTemporaryFile(t *testing.T) { p := testPin(t) // A directory in the pin's place makes the rename fail after the write. if err := os.MkdirAll(filepath.Join(p.Path, "blocker"), 0o755); err != nil { t.Fatalf("block pin path: %v", err) } flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if err := p.Sync(true, flags, flags); err == nil { + if _, err := p.Sync(true, flags, flags); err == nil { t.Fatal("Sync succeeded over a directory") } entries, err := os.ReadDir(filepath.Dir(p.Path)) @@ -124,6 +138,13 @@ func TestPinSyncFailureLeavesNothingInThePolicyDirectory(t *testing.T) { if len(entries) != 1 || entries[0].Name() != filepath.Base(p.Path) { t.Fatalf("policy dir holds %v, want only the blocked pin path", entries) } + entries, err = os.ReadDir(p.StageDir) + if err != nil { + t.Fatalf("read stage dir: %v", err) + } + if len(entries) != 1 || entries[0].Name() != filepath.Base(filepath.Dir(p.Path)) { + t.Fatalf("stage dir holds %v, want only the policy dir", entries) + } } // A filtered session without a proxy to pin would come up with nothing @@ -134,7 +155,7 @@ func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { runtimeProxy := []string{"--proxy-server=http://192.0.2.1:3129"} for _, base := range [][]string{nil, {"--proxy-server="}, {"--proxy-server"}} { p := testPin(t) - if err := p.Sync(true, base, runtimeProxy); err == nil { + if _, err := p.Sync(true, base, runtimeProxy); err == nil { t.Fatalf("Sync(true, %q) succeeded", base) } if present, _ := p.Present(); present { @@ -148,11 +169,11 @@ func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { p := testPin(t) flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if err := p.Sync(true, flags, flags); err != nil { + if _, err := p.Sync(true, flags, flags); err != nil { t.Fatalf("Sync(true): %v", err) } for range 2 { - if err := p.Sync(false, nil, nil); err != nil { + if _, err := p.Sync(false, nil, nil); err != nil { t.Fatalf("Sync(false): %v", err) } present, err := p.Present() @@ -164,3 +185,58 @@ func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { } } } + +// The pin is only as strong as its directory: a user who can write there can +// add a policy file that sorts after the pin, or remove it. +func TestPinSyncClosesThePolicyDirectories(t *testing.T) { + p := testPin(t) + for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { + if err := os.Chmod(dir, 0o777); err != nil { + t.Fatalf("open %s: %v", dir, err) + } + } + flags := []string{"--proxy-server=http://192.0.2.1:3129"} + if _, err := p.Sync(true, flags, flags); err != nil { + t.Fatalf("Sync: %v", err) + } + for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { + info, err := os.Stat(dir) + if err != nil { + t.Fatalf("stat %s: %v", dir, err) + } + if perm := info.Mode().Perm(); perm != 0o755 { + t.Fatalf("%s left with mode %o, want 755", dir, perm) + } + } +} + +// A file at the pin's path that is not a pin must not stop the egress policy +// handler from restarting Chromium to write a real one. +func TestPinPresentRequiresAPin(t *testing.T) { + p := testPin(t) + for _, content := range []string{ + "", + "{}", + `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp"}`, + } { + if err := os.WriteFile(p.Path, []byte(content), 0o644); err != nil { + t.Fatalf("write %q: %v", content, err) + } + present, err := p.Present() + if err != nil { + t.Fatalf("Present(%q): %v", content, err) + } + if present { + t.Fatalf("Present(%q) reported a pin", content) + } + } + flags := []string{"--proxy-server=http://192.0.2.1:3129"} + if _, err := p.Sync(true, flags, flags); err != nil { + t.Fatalf("Sync: %v", err) + } + if present, err := p.Present(); err != nil || !present { + t.Fatalf("Present after Sync = %t, %v", present, err) + } +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 26ff6509..500f934c 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -27235,118 +27235,121 @@ var swaggerSpec = []string{ "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", - "3MPrSTvWIfn48f8/AONqs2vW1wMA", + "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", + "R+542QMA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index e3bd1527..55059bf8 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1759,10 +1759,17 @@ paths: cannot send UDP around the proxy. The pinned proxy server is the one in Chromium's base flags; a `--proxy-server` added at runtime, for example with `PATCH /chromium/flags`, does not replace it while the - session is filtered. If the pin is not already in place, Chromium is - restarted to apply it before the request returns, so filtering a - running session closes its open pages. With `filtered: false` the pin - is removed without a restart. + session is filtered. The pinned bypass list is the + `--proxy-bypass-list` Chromium starts with, keeping only the entries + the session's `private_hosts` would accept: private IPs and CIDRs, and + hostnames with a wildcard only over a registrable domain. Other + entries are sent through the proxy. + If the pin is not already in place, Chromium is restarted to apply it + before the request returns, so filtering a running session closes + its open pages. With `filtered: false` the pin is removed without a + restart; Chromium normally stops applying it within a few seconds, but + a session unfiltered just after Chromium starts can stay pinned for up + to 15 minutes. Idempotent, and applied by the control plane both when the session is From 4164bd989ee101a82796b9fb25ae8f38709ae894 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:45:25 +0000 Subject: [PATCH 07/19] Take the pinned bypass list from the control plane and close host-mapping and DoH routes The pin kept any well-formed hostname from a runtime --proxy-bypass-list, so a runtime bypass list naming an arbitrary public host still sent it around the egress proxy. The control plane now sends the session's private_hosts with the egress policy, which only it can write, and the pin uses exactly those, or the base flags' bypass list when it sends none. The runtime-flag filter is gone. While filtered, the launcher also ignores runtime --host-resolver-rules and --host-rules, which Chromium applies to the egress proxy's own address and which could send every request to a different proxy, and the pin sets DnsOverHttpsMode to off, since Chromium connects to a DoH server directly. The handler restarts Chromium when the pin was written for different private hosts, and the pin is flushed to disk before it is renamed into place. --- server/cmd/api/api/egress_policy.go | 45 +- server/cmd/api/api/egress_policy_test.go | 64 +- server/cmd/chromium-launcher/main.go | 22 +- server/e2e/e2e_egress_proxy_pin_test.go | 34 +- server/go.mod | 2 +- server/lib/egresspolicy/bypass.go | 183 --- server/lib/egresspolicy/bypass_test.go | 75 - server/lib/egresspolicy/egresspolicy.go | 76 +- server/lib/egresspolicy/egresspolicy_test.go | 28 +- server/lib/egresspolicy/pin.go | 96 +- server/lib/egresspolicy/pin_test.go | 139 +- server/lib/oapi/oapi.go | 1403 +++++++++--------- server/openapi.yaml | 41 +- 13 files changed, 1080 insertions(+), 1128 deletions(-) delete mode 100644 server/lib/egresspolicy/bypass.go delete mode 100644 server/lib/egresspolicy/bypass_test.go diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index 3ce57731..cc127835 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -3,14 +3,17 @@ package api import ( "context" "errors" + "strings" + "github.com/kernel/kernel-images/server/lib/egresspolicy" "github.com/kernel/kernel-images/server/lib/logger" oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // GetNetworkEgressPolicy handles GET /network/egress-policy. func (s *ApiService) GetNetworkEgressPolicy(_ context.Context, _ oapi.GetNetworkEgressPolicyRequestObject) (oapi.GetNetworkEgressPolicyResponseObject, error) { - return oapi.GetNetworkEgressPolicy200JSONResponse{Filtered: s.egressPolicy.Filtered()}, nil + policy := s.egressPolicy.Policy() + return oapi.GetNetworkEgressPolicy200JSONResponse{Filtered: policy.Filtered, PrivateHosts: policy.PrivateHosts}, nil } // PutNetworkEgressPolicy handles PUT /network/egress-policy. @@ -23,6 +26,16 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet if req.Body == nil { return oapi.PutNetworkEgressPolicy400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "missing body"}}, nil } + if req.Body.PrivateHosts != nil { + for _, host := range *req.Body.PrivateHosts { + // Chromium splits its bypass list on both separators, so one entry + // carrying either would add rules of its own. + if host == "" || strings.ContainsAny(host, ";, \t\r\n") { + return oapi.PutNetworkEgressPolicy400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "private_hosts entries must be non-empty and contain no ';', ',' or whitespace"}}, nil + } + } + } + policy := egresspolicy.Policy{Filtered: req.Body.Filtered, PrivateHosts: req.Body.PrivateHosts} // The pin is written by the launcher from the flags Chromium starts with, // so applying it means a restart, which must not interleave with another @@ -30,41 +43,45 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet s.chromiumConfigMu.Lock() defer s.chromiumConfigMu.Unlock() + previous := s.egressPolicy.Policy() // A policy that was not persisted would be lost if this process restarted, // so the caller is told it did not apply rather than being left to assume // a refusal is in place. - if err := s.egressPolicy.SetFiltered(req.Body.Filtered); err != nil { - log.Error("failed to apply egress policy", "err", err, "filtered", req.Body.Filtered) + if err := s.egressPolicy.Set(policy); err != nil { + log.Error("failed to apply egress policy", "err", err, "filtered", policy.Filtered) return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil } - if err := s.applyEgressPin(ctx, req.Body.Filtered); err != nil { - log.Error("failed to apply egress proxy pin", "err", err, "filtered", req.Body.Filtered) + if err := s.applyEgressPin(ctx, policy, previous); err != nil { + log.Error("failed to apply egress proxy pin", "err", err, "filtered", policy.Filtered) return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil } - log.Info("egress policy applied", "filtered", req.Body.Filtered) - return oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: req.Body.Filtered}, nil + log.Info("egress policy applied", "filtered", policy.Filtered) + return oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: policy.Filtered, PrivateHosts: policy.PrivateHosts}, nil } // applyEgressPin brings Chromium's proxy pin in line with the policy. The // caller must hold chromiumConfigMu. // -// A missing pin is written by restarting Chromium rather than by waiting for -// it to reload its policy directory, so the pin is in force before the control -// plane hands the session to a client. Chromium does not need a restart for the -// pin to come off. +// A pin that is missing, or was written for a different policy, is rewritten +// by restarting Chromium rather than by waiting for it to reload its policy +// directory, so the pin is in force before the control plane hands the session +// to a client. Chromium does not need a restart for the pin to come off. // // The launcher does not take chromiumConfigMu, so a Chromium that crashed and is // restarting as the policy flips to unfiltered can write the pin again after it // is removed. That leaves the session pinned until Chromium next starts, which // fails closed. -func (s *ApiService) applyEgressPin(ctx context.Context, filtered bool) error { - if !filtered { +func (s *ApiService) applyEgressPin(ctx context.Context, policy, previous egresspolicy.Policy) error { + if !policy.Filtered { return s.egressPin.Remove() } present, err := s.egressPin.Present() - if err != nil || present { + if err != nil { return err } + if present && policy.Equal(previous) { + return nil + } if err := s.restartChromiumAndWait(ctx, "egress policy"); err != nil { return err } diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index 3e6ae245..1bf2747e 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/kernel/kernel-images/server/lib/egresspolicy" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/require" ) @@ -19,17 +20,17 @@ func TestGetNetworkEgressPolicyDefaultsToUnfiltered(t *testing.T) { require.Equal(t, oapi.GetNetworkEgressPolicy200JSONResponse{Filtered: false}, resp) } -// The control plane applies the policy at setup and again whenever an -// allowlist is added to or removed from a running session, so the endpoint has -// to carry a session both ways and tolerate being told the same thing twice. -// The pin is already in place, as the launcher leaves it once Chromium has -// started filtered, so nothing here needs a restart. +// The control plane applies the policy at setup and again whenever a running +// session's allowlist changes, so the endpoint has to carry a session both ways +// and tolerate being told the same thing twice. The session starts filtered +// with its pin in place, as the launcher leaves it, so nothing here needs a +// restart. func TestPutNetworkEgressPolicy(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) - flags := []string{"--proxy-server=http://192.0.2.1:3129"} - _, err = svc.egressPin.Sync(true, flags, flags) - require.NoError(t, err) + filtered := egresspolicy.Policy{Filtered: true} + require.NoError(t, svc.egressPolicy.Set(filtered)) + require.NoError(t, svc.egressPin.Sync(filtered, []string{"--proxy-server=http://192.0.2.1:3129"})) for _, want := range []bool{true, true, false, false} { resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ @@ -44,6 +45,53 @@ func TestPutNetworkEgressPolicy(t *testing.T) { } } +// The pin's bypass list is the policy's private hosts, so a pin written for +// other private hosts is rewritten with a restart, and one written for these +// is left alone. The new hosts are recorded even when the restart fails, so the +// next Chromium start pins them. +func TestPutNetworkEgressPolicyRestartsForChangedPrivateHosts(t *testing.T) { + svc, err := newSvc(t, newMockRecordManager()) + require.NoError(t, err) + before := egresspolicy.Policy{Filtered: true, PrivateHosts: &[]string{"10.1.0.0/16"}} + require.NoError(t, svc.egressPolicy.Set(before)) + require.NoError(t, svc.egressPin.Sync(before, []string{"--proxy-server=http://192.0.2.1:3129"})) + // Leaves supervisorctl off PATH, so a restart fails. + t.Setenv("PATH", t.TempDir()) + + after := []string{"preview.internal:8443"} + resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ + Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &after}, + }) + require.NoError(t, err) + require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp, "changed private hosts did not restart Chromium") + require.Equal(t, &after, svc.egressPolicy.Policy().PrivateHosts) + + // Stands in for the launcher, which writes the pin on the next start. + require.NoError(t, svc.egressPin.Sync(svc.egressPolicy.Policy(), []string{"--proxy-server=http://192.0.2.1:3129"})) + resp, err = svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ + Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &after}, + }) + require.NoError(t, err) + require.Equal(t, oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: true, PrivateHosts: &after}, resp) +} + +// Chromium splits its bypass list on ";" and ",", so an entry carrying either +// would add bypass rules of its own. +func TestPutNetworkEgressPolicyRejectsMalformedPrivateHosts(t *testing.T) { + svc, err := newSvc(t, newMockRecordManager()) + require.NoError(t, err) + + for _, host := range []string{"", "a.internal;*", "a.internal,*", "a.internal *"} { + hosts := []string{"10.1.0.0/16", host} + resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ + Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &hosts}, + }) + require.NoError(t, err) + require.IsType(t, oapi.PutNetworkEgressPolicy400JSONResponse{}, resp, "accepted private host %q", host) + require.False(t, svc.egressPolicy.Filtered()) + } +} + // Without the pin, filtering means restarting Chromium so the launcher writes // it. When that fails the caller is told so, and the session stays filtered: // the next Chromium start pins the proxy, and the CDP proxy keeps refusing diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index 0d307ab1..372a20f2 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -82,24 +82,28 @@ func main() { fmt.Fprintf(os.Stderr, "failed reading runtime flags: %v\n", err) os.Exit(1) } - final := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, runtimeTokens) - final = chromiumflags.TranslateKernelDisableFeatures(final) - final = withDefaultPrivateNetworkBypass(final) // A filtered session must not start without its proxy pinned: an extension // could otherwise switch Chromium to direct connections around the egress // proxy and the allowlist it enforces. - egress := egresspolicy.Load(egresspolicy.DefaultStatePath, slog.New(slog.NewTextHandler(os.Stderr, nil))) - base := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, nil) - dropped, err := egresspolicy.DefaultPin.Sync(egress.Filtered(), base, final) - if len(dropped) > 0 { - fmt.Fprintf(os.Stderr, "egress proxy pin leaves out bypass entries that are not private hosts: %s\n", strings.Join(dropped, ";")) + egress := egresspolicy.Load(egresspolicy.DefaultStatePath, slog.New(slog.NewTextHandler(os.Stderr, nil))).Policy() + if egress.Filtered { + var dropped []string + runtimeTokens, dropped = egresspolicy.DropHostMappingFlags(runtimeTokens) + if len(dropped) > 0 { + fmt.Fprintf(os.Stderr, "egress is filtered; ignoring runtime flags that remap hosts: %s\n", strings.Join(dropped, " ")) + } } - if err != nil { + base := withDefaultPrivateNetworkBypass(chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, nil)) + if err := egresspolicy.DefaultPin.Sync(egress, base); err != nil { fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) os.Exit(1) } + final := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, runtimeTokens) + final = chromiumflags.TranslateKernelDisableFeatures(final) + final = withDefaultPrivateNetworkBypass(final) + // Diagnostics for parity with previous scripts fmt.Printf("BASE_FLAGS: %s\n", baseFlags) fmt.Printf("RUNTIME_FLAGS: %s\n", strings.Join(runtimeTokens, " ")) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index 58d8020b..ba6f290b 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -43,19 +43,28 @@ func TestEgressProxyPin(t *testing.T) { client, err := c.APIClient() require.NoError(t, err) - putEgressPolicy(t, ctx, client, true) + putEgressPolicy(t, ctx, client, instanceoapi.NetworkEgressPolicy{ + Filtered: true, + PrivateHosts: &[]string{"10.1.0.0/16", "example.com:8443"}, + }) // Anyone holding the session's token can write runtime flags and chrome // policy. Neither may undo the pin: each write restarts Chromium, and the - // launcher pins the base flags' proxy, keeps only private hosts in the - // bypass list, and clears per-URL WebRTC rules. + // launcher pins the base flags' proxy and the policy's private hosts, + // clears per-URL WebRTC rules, turns DNS-over-HTTPS off, and leaves out + // runtime flags that remap hosts. policyRsp, err := client.PatchChromiumPoliciesWithResponse(ctx, instanceoapi.PatchChromiumPoliciesJSONRequestBody{ "WebRtcIPHandlingUrl": []map[string]string{{"url": "*", "handling": "default"}}, + "DnsOverHttpsMode": "secure", }) require.NoError(t, err) require.Equal(t, http.StatusOK, policyRsp.StatusCode(), "patch policies: %s", string(policyRsp.Body)) flagsRsp, err := client.PatchChromiumFlagsWithResponse(ctx, instanceoapi.PatchChromiumFlagsJSONRequestBody{ - Flags: []string{"--proxy-server=http://127.0.0.1:8", "--proxy-bypass-list=*;example.com:8443;10.1.0.0/16"}, + Flags: []string{ + "--proxy-server=http://127.0.0.1:8", + "--proxy-bypass-list=*;exfil.example.com", + "--host-resolver-rules=MAP 127.0.0.1 127.0.0.1:8", + }, }) require.NoError(t, err) require.Equal(t, http.StatusOK, flagsRsp.StatusCode(), "patch flags: %s", string(flagsRsp.Body)) @@ -66,17 +75,24 @@ func TestEgressProxyPin(t *testing.T) { ProxySettings map[string]string `json:"ProxySettings"` WebRtcIPHandling string `json:"WebRtcIPHandling"` WebRtcIPHandlingURL []any `json:"WebRtcIPHandlingUrl"` + DnsOverHttpsMode string `json:"DnsOverHttpsMode"` } require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) require.Equal(t, map[string]string{ "ProxyMode": "fixed_servers", "ProxyServer": "http://127.0.0.1:9", - "ProxyBypassList": "example.com:8443;10.1.0.0/16", + "ProxyBypassList": "10.1.0.0/16;example.com:8443", }, pin.ProxySettings) require.Equal(t, "disable_non_proxied_udp", pin.WebRtcIPHandling) require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) + require.Equal(t, "off", pin.DnsOverHttpsMode) + + // The flag's name is split so this script's own command line does not + // match it. + out, err = execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", `! grep -qa -- "--host-resolver""-rules" /proc/[0-9]*/cmdline`}) + require.NoError(t, err, "chromium started with a runtime host mapping rule on a filtered session: %s", out) // The user Chromium runs as must not be able to add a policy file that // sorts after the pin, or remove it. @@ -103,7 +119,7 @@ func TestEgressProxyPin(t *testing.T) { require.Empty(t, udp, "WebRTC gathered UDP candidates on a filtered session") waitForPolicyWatch(t, ctx, c) - putEgressPolicy(t, ctx, client, false) + putEgressPolicy(t, ctx, client, instanceoapi.NetworkEgressPolicy{Filtered: false}) _, err = execCombinedOutputWithClient(ctx, c, "test", []string{"!", "-e", egresspolicy.DefaultPin.Path}) require.NoError(t, err, "pin still present after the session was unfiltered") @@ -126,11 +142,11 @@ func TestEgressProxyPin(t *testing.T) { }, time.Minute, time.Second, "per-URL WebRTC rule still cleared after the session was unfiltered") } -func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, filtered bool) { +func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, policy instanceoapi.NetworkEgressPolicy) { t.Helper() - rsp, err := client.PutNetworkEgressPolicyWithResponse(ctx, instanceoapi.PutNetworkEgressPolicyJSONRequestBody{Filtered: filtered}) + rsp, err := client.PutNetworkEgressPolicyWithResponse(ctx, policy) require.NoError(t, err) - require.Equal(t, http.StatusOK, rsp.StatusCode(), "put egress policy filtered=%t: %s", filtered, string(rsp.Body)) + require.Equal(t, http.StatusOK, rsp.StatusCode(), "put egress policy filtered=%t: %s", policy.Filtered, string(rsp.Body)) } // attachProxyExtensionWorker loads an extension with the proxy and privacy diff --git a/server/go.mod b/server/go.mod index 0937513d..8d55fa81 100644 --- a/server/go.mod +++ b/server/go.mod @@ -34,7 +34,6 @@ require ( go.opentelemetry.io/otel/sdk v1.45.0 go.opentelemetry.io/otel/sdk/log v0.20.0 go.opentelemetry.io/proto/otlp v1.11.0 - golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 golang.org/x/sys v0.47.0 golang.org/x/term v0.45.0 @@ -114,6 +113,7 @@ require ( go.opentelemetry.io/otel/trace v1.45.0 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/text v0.40.0 // indirect golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect diff --git a/server/lib/egresspolicy/bypass.go b/server/lib/egresspolicy/bypass.go deleted file mode 100644 index ebe628c8..00000000 --- a/server/lib/egresspolicy/bypass.go +++ /dev/null @@ -1,183 +0,0 @@ -package egresspolicy - -import ( - "net/netip" - "regexp" - "strconv" - "strings" - - "golang.org/x/net/publicsuffix" -) - -// The rules below mirror the control plane's validation of a session's -// private_hosts, which is the only legitimate source of a runtime -// --proxy-bypass-list. Anyone holding the session's token can write runtime -// flags too, and once a bypass list is pinned it routes traffic around the -// egress proxy, so the pin carries only entries the control plane would have -// accepted. - -const ( - maxBypassEntries = 32 - maxBypassEntryLen = 255 -) - -var ( - bypassEntryRegex = regexp.MustCompile(`^[A-Za-z0-9.*:/\[\]_-]+$`) - bypassLabelRegex = regexp.MustCompile(`^[A-Za-z0-9_](?:[A-Za-z0-9_-]{0,61}[A-Za-z0-9_])?$`) -) - -// privateRanges are the only ranges an IP or CIDR entry may cover: RFC1918, -// CGNAT, and IPv6 ULA. -var privateRanges = []netip.Prefix{ - netip.MustParsePrefix("10.0.0.0/8"), - netip.MustParsePrefix("172.16.0.0/12"), - netip.MustParsePrefix("192.168.0.0/16"), - netip.MustParsePrefix("100.64.0.0/10"), - netip.MustParsePrefix("fc00::/7"), -} - -// privateBypassList keeps the entries of a --proxy-bypass-list value that the -// control plane would accept as private hosts, and returns the rest as -// dropped. Dropping an entry sends its traffic through the egress proxy, so a -// rejected entry fails closed. -func privateBypassList(list string) (kept string, dropped []string) { - var entries []string - for _, entry := range strings.Split(list, ";") { - entry = strings.TrimSpace(entry) - if entry == "" { - continue - } - if len(entries) < maxBypassEntries && validBypassEntry(entry) { - entries = append(entries, entry) - } else { - dropped = append(dropped, entry) - } - } - return strings.Join(entries, ";"), dropped -} - -func validBypassEntry(entry string) bool { - if len(entry) > maxBypassEntryLen || !bypassEntryRegex.MatchString(entry) || strings.Contains(entry, "://") { - return false - } - if strings.Contains(entry, "/") { - prefix, err := netip.ParsePrefix(entry) - return err == nil && !prefix.Addr().Is4In6() && prefix == prefix.Masked() && prefixIsPrivate(prefix) - } - host, ok := hostWithoutPort(entry) - if !ok { - return false - } - if addr, err := netip.ParseAddr(host); err == nil { - return !addr.Is4In6() && addrIsPrivate(addr) - } - if isNumericIPv4(host) { - return false - } - if wildcards := strings.Count(host, "*"); wildcards > 0 { - if wildcards != 1 || !strings.HasPrefix(host, "*.") { - return false - } - host = strings.TrimPrefix(host, "*.") - if strings.Count(host, ".") < 1 || isPublicSuffix(host) { - return false - } - } - return validHostname(host) -} - -func prefixIsPrivate(prefix netip.Prefix) bool { - for _, r := range privateRanges { - if r.Bits() <= prefix.Bits() && r.Contains(prefix.Addr()) { - return true - } - } - return false -} - -func addrIsPrivate(addr netip.Addr) bool { - for _, r := range privateRanges { - if r.Contains(addr) { - return true - } - } - return false -} - -func hostWithoutPort(entry string) (string, bool) { - if strings.HasPrefix(entry, "[") { - end := strings.IndexByte(entry, ']') - if end < 0 { - return "", false - } - host := entry[1:end] - if addr, err := netip.ParseAddr(host); err != nil || !addr.Is6() { - return "", false - } - rest := entry[end+1:] - if rest == "" { - return host, true - } - if !strings.HasPrefix(rest, ":") || strings.ContainsAny(rest[1:], "[]:") { - return "", false - } - return host, validPort(rest[1:]) - } - if strings.ContainsAny(entry, "[]") { - return "", false - } - if addr, err := netip.ParseAddr(entry); err == nil { - // Chromium only matches an exact IPv6 address in bracketed form. - return entry, !addr.Is6() || addr.Is4In6() - } - if strings.Count(entry, ":") > 1 { - return "", false - } - if i := strings.LastIndexByte(entry, ':'); i >= 0 { - return entry[:i], validPort(entry[i+1:]) - } - return entry, true -} - -func validPort(value string) bool { - port, err := strconv.ParseUint(value, 10, 16) - return err == nil && port != 0 -} - -// isNumericIPv4 reports whether Chromium would parse host as an IPv4 address -// rather than a hostname, as it does with forms such as 0x7f.1. -func isNumericIPv4(host string) bool { - for _, part := range strings.Split(host, ".") { - if part == "" { - return false - } - digits := "0123456789" - if strings.HasPrefix(part, "0x") || strings.HasPrefix(part, "0X") { - part, digits = part[2:], "0123456789abcdefABCDEF" - } - for _, c := range part { - if !strings.ContainsRune(digits, c) { - return false - } - } - } - return true -} - -func isPublicSuffix(host string) bool { - host = strings.ToLower(host) - suffix, _ := publicsuffix.PublicSuffix(host) - return suffix == host -} - -func validHostname(host string) bool { - if host == "" || strings.HasPrefix(host, ".") || strings.HasSuffix(host, ".") || strings.Contains(host, "..") { - return false - } - for _, label := range strings.Split(host, ".") { - if !bypassLabelRegex.MatchString(label) { - return false - } - } - return true -} diff --git a/server/lib/egresspolicy/bypass_test.go b/server/lib/egresspolicy/bypass_test.go deleted file mode 100644 index ee014e82..00000000 --- a/server/lib/egresspolicy/bypass_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package egresspolicy - -import ( - "slices" - "testing" -) - -// Runtime flags can be written by anyone holding the session's token, so the -// pin keeps only the bypass entries the control plane accepts as private hosts. -func TestPrivateBypassList(t *testing.T) { - accepted := []string{ - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.1.0/24", - "100.64.0.0/10", - "fc00::/7", - "fd00::/8", - "10.1.2.3", - "10.1.2.3:8080", - "[fd00::1]", - "[fd00::1]:8443", - "preview.internal", - "preview.internal:8443", - "*.corp.example.com", - "under_score.internal", - } - rejected := []string{ - "*", - "", - "<-loopback>", - "*.com", - "*.co.uk", - "*.internal", - "*example.com", - "a.*.example.com", - "8.8.8.8", - "8.8.8.0/24", - "0.0.0.0/0", - "10.0.0.1/8", - "127.0.0.1", - "169.254.169.254", - "fd00::1", - "::ffff:10.0.0.1", - "0x0a.1.2.3", - "http://preview.internal", - "preview.internal:0", - "preview.internal:65536", - "-bad.internal", - "bad..internal", - } - for _, entry := range accepted { - if kept, dropped := privateBypassList(entry); kept != entry || len(dropped) != 0 { - t.Errorf("privateBypassList(%q) = %q, %q; want it kept", entry, kept, dropped) - } - } - for _, entry := range rejected { - if kept, dropped := privateBypassList(entry); kept != "" || !slices.Equal(dropped, []string{entry}) { - t.Errorf("privateBypassList(%q) = %q, %q; want it dropped", entry, kept, dropped) - } - } -} - -func TestPrivateBypassListCapsEntries(t *testing.T) { - var list string - for i := range maxBypassEntries + 1 { - if i > 0 { - list += ";" - } - list += "10.0.0." + string(rune('0'+i%10)) - } - _, dropped := privateBypassList(list) - if len(dropped) != 1 { - t.Fatalf("dropped %d entries, want 1 past the cap of %d", len(dropped), maxBypassEntries) - } -} diff --git a/server/lib/egresspolicy/egresspolicy.go b/server/lib/egresspolicy/egresspolicy.go index 99f2345a..aaa97ef6 100644 --- a/server/lib/egresspolicy/egresspolicy.go +++ b/server/lib/egresspolicy/egresspolicy.go @@ -3,10 +3,11 @@ // // The policy itself lives outside the VM: a session's allowlist is enforced by // Kernel's egress proxy, which is the only component that knows which -// destinations are allowed. The VM is told one thing — that an allowlist -// exists — because some of what it can be asked to do would route traffic -// around that proxy, and it cannot refuse those requests without knowing the -// session is filtered at all. +// destinations are allowed. The VM is told that an allowlist exists, because +// some of what it can be asked to do would route traffic around that proxy, +// and it cannot refuse those requests without knowing the session is filtered +// at all. It is also told the session's private hosts, the destinations that +// are meant to bypass the proxy, so that it can pin exactly those. package egresspolicy import ( @@ -15,6 +16,7 @@ import ( "log/slog" "os" "path/filepath" + "slices" "sync" "sync/atomic" ) @@ -24,8 +26,22 @@ import ( // identity, since both are per-boot state the control plane hands the VM. const DefaultStatePath = "/run/kernel/egress-policy.json" -type persisted struct { +// Policy is what the control plane tells the VM about its session's egress. +type Policy struct { + // Filtered is whether the session's egress is restricted to an allowlist. Filtered bool `json:"filtered"` + // PrivateHosts is the session's private_hosts, which bypass the egress + // proxy. Nil leaves the image's default bypass in place; an empty list + // bypasses nothing. + PrivateHosts *[]string `json:"private_hosts,omitempty"` +} + +// Equal reports whether p and o are the same policy. +func (p Policy) Equal(o Policy) bool { + if p.Filtered != o.Filtered || (p.PrivateHosts == nil) != (o.PrivateHosts == nil) { + return false + } + return p.PrivateHosts == nil || slices.Equal(*p.PrivateHosts, *o.PrivateHosts) } // State is the session's egress policy as the control plane last reported it. @@ -38,8 +54,9 @@ type State struct { path string // mu serializes a write with the store that follows it, so the file and // the memory copy cannot disagree about which update came last. - mu sync.Mutex - filtered atomic.Bool + mu sync.Mutex + filtered atomic.Bool + privateHosts *[]string } // Load reads the policy left by a previous run of the process. A session that @@ -60,13 +77,14 @@ func Load(path string, logger *slog.Logger) *State { s.filtered.Store(true) return s } - var p persisted + var p Policy if err := json.Unmarshal(data, &p); err != nil { logger.Error("could not decode the egress policy; treating the session as filtered", "path", path, "err", err) s.filtered.Store(true) return s } s.filtered.Store(p.Filtered) + s.privateHosts = p.PrivateHosts return s } @@ -77,25 +95,45 @@ func (s *State) Filtered() bool { return s != nil && s.filtered.Load() } -// SetFiltered records the policy the control plane applied, and does not take -// effect unless it was persisted: a caller that is told the policy is applied -// must be able to rely on it surviving a restart. +// Policy returns the policy as the control plane last reported it. +func (s *State) Policy() Policy { + s.mu.Lock() + defer s.mu.Unlock() + return Policy{Filtered: s.filtered.Load(), PrivateHosts: cloneHosts(s.privateHosts)} +} + +// Set records the policy the control plane applied, and does not take effect +// unless it was persisted: a caller that is told the policy is applied must be +// able to rely on it surviving a restart. // -// It is called when the session is set up and again whenever an allowlist is -// added to or removed from a running session, so it is safe to call repeatedly -// with the same value. -func (s *State) SetFiltered(filtered bool) error { +// It is called when the session is set up and again whenever a running +// session's allowlist changes, so it is safe to call repeatedly with the same +// value. +func (s *State) Set(p Policy) error { + p.PrivateHosts = cloneHosts(p.PrivateHosts) s.mu.Lock() defer s.mu.Unlock() - if err := s.persist(filtered); err != nil { + if err := s.persist(p); err != nil { return err } - s.filtered.Store(filtered) + s.filtered.Store(p.Filtered) + s.privateHosts = p.PrivateHosts return nil } -func (s *State) persist(filtered bool) error { - data, err := json.Marshal(persisted{Filtered: filtered}) +func cloneHosts(hosts *[]string) *[]string { + if hosts == nil { + return nil + } + c := slices.Clone(*hosts) + if c == nil { + c = []string{} + } + return &c +} + +func (s *State) persist(p Policy) error { + data, err := json.Marshal(p) if err != nil { return fmt.Errorf("encode egress policy: %w", err) } diff --git a/server/lib/egresspolicy/egresspolicy_test.go b/server/lib/egresspolicy/egresspolicy_test.go index d6d29c0e..8b4c35cc 100644 --- a/server/lib/egresspolicy/egresspolicy_test.go +++ b/server/lib/egresspolicy/egresspolicy_test.go @@ -31,7 +31,7 @@ func TestLoadWithoutStateIsUnfiltered(t *testing.T) { func TestPolicySurvivesProcessRestart(t *testing.T) { path := statePath(t) applied := Load(path, silent()) - if err := applied.SetFiltered(true); err != nil { + if err := applied.Set(Policy{Filtered: true}); err != nil { t.Fatalf("set filtered: %v", err) } @@ -40,7 +40,7 @@ func TestPolicySurvivesProcessRestart(t *testing.T) { t.Fatal("policy did not survive a restart") } - if err := restarted.SetFiltered(false); err != nil { + if err := restarted.Set(Policy{}); err != nil { t.Fatalf("clear filtered: %v", err) } if Load(path, silent()).Filtered() { @@ -62,7 +62,7 @@ func TestLoadWithUnreadableStateIsFiltered(t *testing.T) { // A policy that was not written down is not applied, so the control plane is // told it failed rather than assuming a refusal is in place. -func TestSetFilteredFailsWhenItCannotPersist(t *testing.T) { +func TestSetFailsWhenItCannotPersist(t *testing.T) { dir := t.TempDir() state := Load(filepath.Join(dir, "egress-policy.json"), silent()) // Take away the right to write the file, leaving the path itself valid. @@ -71,10 +71,30 @@ func TestSetFilteredFailsWhenItCannotPersist(t *testing.T) { } t.Cleanup(func() { _ = os.Chmod(dir, 0o755) }) - if err := state.SetFiltered(true); err == nil { + if err := state.Set(Policy{Filtered: true}); err == nil { t.Fatal("a policy that could not be persisted reported success") } if state.Filtered() { t.Fatal("a policy that could not be persisted was applied in memory") } } + +// The launcher pins the private hosts it reads back, so a restart must keep +// "no list" and "an empty list" apart: the first keeps the image's default +// bypass, the second bypasses nothing. +func TestPrivateHostsSurviveProcessRestart(t *testing.T) { + hosts := []string{"10.1.0.0/16", "preview.internal:8443"} + for _, want := range []Policy{ + {Filtered: true}, + {Filtered: true, PrivateHosts: &[]string{}}, + {Filtered: true, PrivateHosts: &hosts}, + } { + path := statePath(t) + if err := Load(path, silent()).Set(want); err != nil { + t.Fatalf("Set(%+v): %v", want, err) + } + if got := Load(path, silent()).Policy(); !got.Equal(want) { + t.Fatalf("policy after restart = %+v, want %+v", got, want) + } + } +} diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 6793c697..927b59b3 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "syscall" ) @@ -38,6 +39,11 @@ var DefaultPin = Pin{ // the per-URL WebRtcIPHandlingUrl rules, which Chromium consults before // WebRtcIPHandling and which a chrome_policy override could otherwise use to // turn direct UDP back on. +// +// Chromium connects to a DNS-over-HTTPS server directly rather than through +// the proxy, whenever it resolves a name itself, as it does for a bypassed +// hostname. The pin turns DNS-over-HTTPS off, so a chrome_policy override +// cannot name a server for it to connect to. type Pin struct { // Path is the pin's file in Chromium's managed policy directory. Path string @@ -51,6 +57,7 @@ type pinPolicy struct { ProxySettings proxySettings `json:"ProxySettings"` WebRtcIPHandling string `json:"WebRtcIPHandling"` WebRtcIPHandlingURL []json.RawMessage `json:"WebRtcIPHandlingUrl"` + DnsOverHttpsMode string `json:"DnsOverHttpsMode"` } type proxySettings struct { @@ -62,54 +69,99 @@ type proxySettings struct { const ( pinProxyMode = "fixed_servers" pinWebRtcIPHandling = "disable_non_proxied_udp" + pinDnsOverHttpsMode = "off" ) // Sync writes the pin when the session is filtered, and removes it when it is -// not. It returns the bypass list entries it left out of the pin. +// not. base is the flags the control plane launches the VM with, before any +// runtime flags are merged in. // -// The proxy server is taken from base, the flags the control plane launches -// the VM with, never from runtime flags: anyone holding the session's token -// can write those, and a runtime --proxy-server would otherwise be pinned in -// place of the egress proxy. The bypass list is taken from final, the flags -// Chromium is about to start with, because the control plane sets a session's -// private hosts at runtime; for the same reason it keeps only entries the -// control plane would accept as private hosts. +// Nothing in the pin comes from runtime flags: anyone holding the session's +// token can write those, and a pinned runtime --proxy-server or +// --proxy-bypass-list would route traffic around the egress proxy. The proxy +// server is the base flags' own. The bypass list is the policy's private hosts, +// which only the control plane can set, or the base flags' bypass list when the +// control plane set none. // // The policy directory, and the staging directory the pin is renamed from, // are made the writer's own and closed to everyone else, so the user Chromium // runs as cannot add a policy file that sorts after the pin or remove it. -func (p Pin) Sync(filtered bool, base, final []string) ([]string, error) { - if !filtered { - return nil, p.Remove() +func (p Pin) Sync(policy Policy, base []string) error { + if !policy.Filtered { + return p.Remove() } server, ok := lastFlagValue(base, "--proxy-server") if !ok || server == "" { - return nil, errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") + return errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") + } + var bypass string + if policy.PrivateHosts != nil { + bypass = strings.Join(*policy.PrivateHosts, ";") + } else { + bypass, _ = lastFlagValue(base, "--proxy-bypass-list") } - rawBypass, _ := lastFlagValue(final, "--proxy-bypass-list") - bypass, dropped := privateBypassList(rawBypass) data, err := json.Marshal(pinPolicy{ ProxySettings: proxySettings{ProxyMode: pinProxyMode, ProxyServer: server, ProxyBypassList: bypass}, WebRtcIPHandling: pinWebRtcIPHandling, WebRtcIPHandlingURL: []json.RawMessage{}, + DnsOverHttpsMode: pinDnsOverHttpsMode, }) if err != nil { - return dropped, fmt.Errorf("encode proxy pin: %w", err) + return fmt.Errorf("encode proxy pin: %w", err) } for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { if err := ownDir(dir); err != nil { - return dropped, err + return err } } tmp := filepath.Join(p.StageDir, filepath.Base(p.Path)+".tmp") defer os.Remove(tmp) - if err := os.WriteFile(tmp, append(data, '\n'), 0o644); err != nil { - return dropped, fmt.Errorf("write proxy pin: %w", err) + if err := writeSynced(tmp, append(data, '\n')); err != nil { + return fmt.Errorf("write proxy pin: %w", err) } if err := os.Rename(tmp, p.Path); err != nil { - return dropped, fmt.Errorf("replace proxy pin: %w", err) + return fmt.Errorf("replace proxy pin: %w", err) + } + return nil +} + +// writeSynced writes data to path and flushes it to disk, so a crash after the +// rename that follows cannot leave a torn pin in the policy directory. +func writeSynced(path string, data []byte) error { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) + if err != nil { + return err + } + if _, err := f.Write(data); err != nil { + f.Close() + return err + } + if err := f.Sync(); err != nil { + f.Close() + return err + } + return f.Close() +} + +// hostMappingFlags rewrite the address Chromium connects to for a host, +// including the egress proxy's own address, so a runtime rule could send every +// request to a different proxy. Chromium honors them whatever the pin says. +var hostMappingFlags = []string{"host-resolver-rules", "host-rules"} + +// DropHostMappingFlags returns runtime flags without the ones that remap +// hosts, and the flags it removed. The launcher applies it while the session +// is filtered. Switches are matched by name whatever their dash prefix, +// since Chromium accepts "-name" as well as "--name". +func DropHostMappingFlags(runtime []string) (kept, dropped []string) { + for _, flag := range runtime { + name, _, _ := strings.Cut(strings.TrimLeft(flag, "-"), "=") + if slices.Contains(hostMappingFlags, name) { + dropped = append(dropped, flag) + continue + } + kept = append(kept, flag) } - return dropped, nil + return kept, dropped } // Present reports whether a pin is in place. A file at the pin's path that is @@ -126,6 +178,7 @@ func (p Pin) Present() (bool, error) { ProxySettings proxySettings `json:"ProxySettings"` WebRtcIPHandling string `json:"WebRtcIPHandling"` WebRtcIPHandlingURL *[]json.RawMessage `json:"WebRtcIPHandlingUrl"` + DnsOverHttpsMode string `json:"DnsOverHttpsMode"` } if json.Unmarshal(data, &pinned) != nil { return false, nil @@ -133,7 +186,8 @@ func (p Pin) Present() (bool, error) { return pinned.ProxySettings.ProxyMode == pinProxyMode && pinned.ProxySettings.ProxyServer != "" && pinned.WebRtcIPHandling == pinWebRtcIPHandling && - pinned.WebRtcIPHandlingURL != nil && len(*pinned.WebRtcIPHandlingURL) == 0, nil + pinned.WebRtcIPHandlingURL != nil && len(*pinned.WebRtcIPHandlingURL) == 0 && + pinned.DnsOverHttpsMode == pinDnsOverHttpsMode, nil } // Remove deletes the pin. Chromium reloads its policy directory on its own, so diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index f59d51d9..c312bea5 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "slices" "testing" ) @@ -29,8 +30,8 @@ func readPin(t *testing.T, p Pin) proxySettings { if err := json.Unmarshal(data, &policy); err != nil { t.Fatalf("decode pin: %v", err) } - if len(policy) != 3 { - t.Fatalf("pin sets %d policies, want ProxySettings, WebRtcIPHandling and WebRtcIPHandlingUrl: %s", len(policy), data) + if len(policy) != 4 { + t.Fatalf("pin sets %d policies, want ProxySettings, WebRtcIPHandling, WebRtcIPHandlingUrl and DnsOverHttpsMode: %s", len(policy), data) } if string(policy["WebRtcIPHandlingUrl"]) != "[]" { t.Fatalf("pin leaves per-URL WebRTC rules in place: %s", data) @@ -42,73 +43,61 @@ func readPin(t *testing.T, p Pin) proxySettings { if pinned.WebRtcIPHandling != "disable_non_proxied_udp" { t.Fatalf("pin lets WebRTC send UDP around the proxy: %s", data) } + if pinned.DnsOverHttpsMode != "off" { + t.Fatalf("pin lets Chromium connect to a DNS-over-HTTPS server around the proxy: %s", data) + } return pinned.ProxySettings } // Once the pin is in place Chromium ignores the proxy flags entirely, so the -// pin has to reproduce the egress proxy from the base flags and the bypass list -// Chromium starts with. -func TestPinSyncFollowsChromiumFlags(t *testing.T) { +// pin has to reproduce the egress proxy from the base flags, and the bypass list +// from the private hosts the control plane sent, or from the base flags when it +// sent none. +func TestPinSyncFollowsBaseFlagsAndPolicy(t *testing.T) { const proxy = "--proxy-server=http://192.0.2.1:3129" + const defaultBypass = "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7" + private := []string{"10.1.0.0/16", "preview.internal:8443"} tests := []struct { - name string - base []string - final []string - want proxySettings + name string + base []string + policy Policy + want proxySettings }{ { - name: "image default bypass", - base: []string{"--kiosk", proxy}, - final: []string{"--kiosk", proxy, "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, - }, - { - name: "runtime bypass list after base flags wins", - base: []string{proxy, "--proxy-bypass-list=10.0.0.0/8"}, - final: []string{proxy, "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list=preview.internal"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "preview.internal"}, - }, - { - name: "bypass entries that are not private hosts are left out", - base: []string{proxy}, - final: []string{proxy, "--proxy-bypass-list=*;;*.com;8.8.8.0/24;10.1.0.0/16;preview.internal:8443"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.1.0.0/16;preview.internal:8443"}, + name: "no private hosts keeps the image default bypass", + base: []string{"--kiosk", proxy, defaultBypass}, + policy: Policy{Filtered: true}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7"}, }, { - name: "bypass list with nothing private pins no bypass", - base: []string{proxy}, - final: []string{proxy, "--proxy-bypass-list=*"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + name: "private hosts replace the base bypass list", + base: []string{proxy, defaultBypass}, + policy: Policy{Filtered: true, PrivateHosts: &private}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129", ProxyBypassList: "10.1.0.0/16;preview.internal:8443"}, }, { - name: "runtime proxy server is not pinned", - base: []string{"--proxy-server=http://old:1", proxy}, - final: []string{"--proxy-server=http://old:1", proxy, "--proxy-server=direct://"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + name: "empty private hosts bypass nothing", + base: []string{proxy, defaultBypass}, + policy: Policy{Filtered: true, PrivateHosts: &[]string{}}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, { - name: "explicit empty bypass list", - base: []string{proxy}, - final: []string{proxy, "--proxy-bypass-list=10.0.0.0/8", "--proxy-bypass-list="}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + name: "last base proxy server wins", + base: []string{"--proxy-server=http://old:1", proxy}, + policy: Policy{Filtered: true}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, { - name: "bare empty bypass list", - base: []string{proxy}, - final: []string{proxy, "--proxy-bypass-list"}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, - }, - { - name: "no bypass list", - base: []string{proxy}, - final: []string{proxy}, - want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, + name: "bare empty base bypass list", + base: []string{proxy, "--proxy-bypass-list"}, + policy: Policy{Filtered: true}, + want: proxySettings{ProxyMode: "fixed_servers", ProxyServer: "http://192.0.2.1:3129"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := testPin(t) - if _, err := p.Sync(true, tt.base, tt.final); err != nil { + if err := p.Sync(tt.policy, tt.base); err != nil { t.Fatalf("Sync: %v", err) } if got := readPin(t, p); got != tt.want { @@ -118,6 +107,28 @@ func TestPinSyncFollowsChromiumFlags(t *testing.T) { } } +// Chromium resolves the egress proxy's own address through host mapping rules, +// whatever the pin says, so a runtime rule could send every request to another +// proxy. +func TestDropHostMappingFlags(t *testing.T) { + kept, dropped := DropHostMappingFlags([]string{ + "--kiosk", + "--host-resolver-rules=MAP 192.0.2.1 198.51.100.7:8080", + "--host-rules=MAP * 198.51.100.7", + "-host-resolver-rules=MAP * 198.51.100.7", + "--host-resolver-rules", + "--host-resolver-rules-extra=1", + "--proxy-server=http://198.51.100.7:8080", + }) + wantKept := []string{"--kiosk", "--host-resolver-rules-extra=1", "--proxy-server=http://198.51.100.7:8080"} + if !slices.Equal(kept, wantKept) { + t.Fatalf("kept = %q, want %q", kept, wantKept) + } + if len(dropped) != 4 { + t.Fatalf("dropped = %q, want the four host mapping flags", dropped) + } +} + // Chromium applies every file in the policy directory, whatever its name, so a // write that fails before the rename must not leave its temporary file there, // nor in the staging directory, from where a later rename could move it. @@ -127,8 +138,7 @@ func TestPinSyncFailureLeavesNoTemporaryFile(t *testing.T) { if err := os.MkdirAll(filepath.Join(p.Path, "blocker"), 0o755); err != nil { t.Fatalf("block pin path: %v", err) } - flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if _, err := p.Sync(true, flags, flags); err == nil { + if err := p.Sync(Policy{Filtered: true}, []string{"--proxy-server=http://192.0.2.1:3129"}); err == nil { t.Fatal("Sync succeeded over a directory") } entries, err := os.ReadDir(filepath.Dir(p.Path)) @@ -149,17 +159,14 @@ func TestPinSyncFailureLeavesNoTemporaryFile(t *testing.T) { // A filtered session without a proxy to pin would come up with nothing // stopping an extension from going direct, so the launcher must not start it. -// A --proxy-server that only the runtime flags carry does not count: anyone -// holding the session's token can write those. func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { - runtimeProxy := []string{"--proxy-server=http://192.0.2.1:3129"} for _, base := range [][]string{nil, {"--proxy-server="}, {"--proxy-server"}} { p := testPin(t) - if _, err := p.Sync(true, base, runtimeProxy); err == nil { - t.Fatalf("Sync(true, %q) succeeded", base) + if err := p.Sync(Policy{Filtered: true}, base); err == nil { + t.Fatalf("Sync(filtered, %q) succeeded", base) } if present, _ := p.Present(); present { - t.Fatalf("Sync(true, %q) wrote a pin", base) + t.Fatalf("Sync(filtered, %q) wrote a pin", base) } } } @@ -168,12 +175,11 @@ func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { // extension the customer chose can set it again. func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { p := testPin(t) - flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if _, err := p.Sync(true, flags, flags); err != nil { - t.Fatalf("Sync(true): %v", err) + if err := p.Sync(Policy{Filtered: true}, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { + t.Fatalf("Sync(filtered): %v", err) } for range 2 { - if _, err := p.Sync(false, nil, nil); err != nil { + if err := p.Sync(Policy{}, nil); err != nil { t.Fatalf("Sync(false): %v", err) } present, err := p.Present() @@ -195,8 +201,7 @@ func TestPinSyncClosesThePolicyDirectories(t *testing.T) { t.Fatalf("open %s: %v", dir, err) } } - flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if _, err := p.Sync(true, flags, flags); err != nil { + if err := p.Sync(Policy{Filtered: true}, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { t.Fatalf("Sync: %v", err) } for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { @@ -217,9 +222,10 @@ func TestPinPresentRequiresAPin(t *testing.T) { for _, content := range []string{ "", "{}", - `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[]}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp"}`, + `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp","DnsOverHttpsMode":"off"}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, } { if err := os.WriteFile(p.Path, []byte(content), 0o644); err != nil { t.Fatalf("write %q: %v", content, err) @@ -232,8 +238,7 @@ func TestPinPresentRequiresAPin(t *testing.T) { t.Fatalf("Present(%q) reported a pin", content) } } - flags := []string{"--proxy-server=http://192.0.2.1:3129"} - if _, err := p.Sync(true, flags, flags); err != nil { + if err := p.Sync(Policy{Filtered: true}, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { t.Fatalf("Sync: %v", err) } if present, err := p.Present(); err != nil || !present { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 500f934c..eb5bf2fd 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6530,6 +6530,9 @@ type MovePathRequest struct { type NetworkEgressPolicy struct { // Filtered Whether the session's egress is restricted to an allowlist. When true, the VM refuses requests that would reach destinations without going through the proxy that enforces it. Filtered bool `json:"filtered"` + + // PrivateHosts The session's `network.private_hosts`, which bypass the egress proxy while the session is filtered. Omitted keeps the image's default bypass of private ranges; an empty list bypasses nothing. Entries are passed to Chromium's bypass list as validated by the control plane, and may not contain `;`, `,` or whitespace. + PrivateHosts *[]string `json:"private_hosts,omitempty"` } // OkResponse Generic OK response. @@ -26639,717 +26642,717 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "R+542QMA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 55059bf8..40c8d220 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1752,24 +1752,19 @@ paths: answers it with a protocol error instead of forwarding it to Chromium. - With `filtered: true` the VM also pins Chromium's proxy with the - managed `ProxySettings` policy, so an extension with the `proxy` - permission cannot switch Chromium to direct connections, and with the - `WebRtcIPHandling` and `WebRtcIPHandlingUrl` policies, so WebRTC - cannot send UDP around the proxy. The pinned proxy server is the one - in Chromium's base flags; a `--proxy-server` added at runtime, for - example with `PATCH /chromium/flags`, does not replace it while the - session is filtered. The pinned bypass list is the - `--proxy-bypass-list` Chromium starts with, keeping only the entries - the session's `private_hosts` would accept: private IPs and CIDRs, and - hostnames with a wildcard only over a registrable domain. Other - entries are sent through the proxy. - If the pin is not already in place, Chromium is restarted to apply it - before the request returns, so filtering a running session closes - its open pages. With `filtered: false` the pin is removed without a - restart; Chromium normally stops applying it within a few seconds, but - a session unfiltered just after Chromium starts can stay pinned for up - to 15 minutes. + With `filtered: true` the VM also pins Chromium's network settings + with managed policy, which neither extensions nor `chrome_policy` + overrides can change: `ProxySettings` holds Chromium on the proxy in + its base flags, with `private_hosts` as the bypass list; + `WebRtcIPHandling` and an empty `WebRtcIPHandlingUrl` keep WebRTC + from sending UDP around the proxy; and `DnsOverHttpsMode` is `off`. + While filtered, the proxy and bypass list come only from the base + flags and this request, and runtime flags that remap hosts + (`--host-resolver-rules`, `--host-rules`) are ignored. + If the pin is not already in place for this policy, Chromium is + restarted to apply it before the request returns. With + `filtered: false` the pin is removed without a restart, and + Chromium stops applying it on its next policy reload. Idempotent, and applied by the control plane both when the session is @@ -6617,6 +6612,16 @@ components: Whether the session's egress is restricted to an allowlist. When true, the VM refuses requests that would reach destinations without going through the proxy that enforces it. + private_hosts: + type: array + items: + type: string + description: >- + The session's `network.private_hosts`, which bypass the egress + proxy while the session is filtered. Omitted keeps the image's + default bypass of private ranges; an empty list bypasses nothing. + Entries are passed to Chromium's bypass list as validated by the + control plane, and may not contain `;`, `,` or whitespace. additionalProperties: false TelemetryState: type: object From 6efa5ec4c7e16a7c7021d4c1fdf0c2ac0d28f814 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:22:36 +0000 Subject: [PATCH 08/19] Check the egress pin against the policy instead of the recorded policy The handler skipped the restart when a pin was present and the recorded policy was unchanged, but the policy is recorded before the pin is applied. A restart that failed before the launcher ran left the old pin in place, and a retry of the same request then reported it applied. Compare the pin on disk with the one the launcher would write for the policy and the base flags, which both now derive through egresspolicy.BaseFlags. --- server/cmd/api/api/api.go | 4 + server/cmd/api/api/api_test.go | 1 + server/cmd/api/api/egress_policy.go | 15 ++- server/cmd/api/api/egress_policy_test.go | 34 +++--- server/cmd/chromium-launcher/main.go | 16 +-- server/cmd/chromium-launcher/main_test.go | 60 ---------- server/lib/egresspolicy/egresspolicy.go | 8 -- server/lib/egresspolicy/egresspolicy_test.go | 3 +- server/lib/egresspolicy/pin.go | 102 +++++++++++------ server/lib/egresspolicy/pin_test.go | 110 ++++++++++++++++--- 10 files changed, 196 insertions(+), 157 deletions(-) diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index 860daf67..83c61c9a 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -145,6 +145,9 @@ type ApiService struct { // while the session is filtered. The launcher writes it; the egress policy // handler restarts Chromium to get it written and removes it. egressPin egresspolicy.Pin + // chromiumBaseFlags are the flags the launcher derives the pin from, so the + // handler can tell whether the pin in place is the one the policy needs. + chromiumBaseFlags []string lifecycleCtx context.Context lifecycleCancel context.CancelFunc @@ -204,6 +207,7 @@ func New( s2Storage: s2Storage, egressPolicy: egressPolicy, egressPin: egresspolicy.DefaultPin, + chromiumBaseFlags: egresspolicy.BaseFlags(os.Getenv("CHROMIUM_FLAGS")), webmcp: webmcpclient.NewManager(upstreamMgr), browserRepl: newBrowserReplManager(), lifecycleCtx: ctx, diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index 8e7be05f..8c143489 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -418,6 +418,7 @@ func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { return nil, err } svc.egressPin = egresspolicy.Pin{Path: filepath.Join(managed, "zz-kernel-egress.json"), StageDir: policies} + svc.chromiumBaseFlags = egresspolicy.BaseFlags("--proxy-server=http://192.0.2.1:3129") return svc, nil } diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index cc127835..0903c32e 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -43,7 +43,6 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet s.chromiumConfigMu.Lock() defer s.chromiumConfigMu.Unlock() - previous := s.egressPolicy.Policy() // A policy that was not persisted would be lost if this process restarted, // so the caller is told it did not apply rather than being left to assume // a refusal is in place. @@ -51,7 +50,7 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet log.Error("failed to apply egress policy", "err", err, "filtered", policy.Filtered) return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil } - if err := s.applyEgressPin(ctx, policy, previous); err != nil { + if err := s.applyEgressPin(ctx, policy); err != nil { log.Error("failed to apply egress proxy pin", "err", err, "filtered", policy.Filtered) return oapi.PutNetworkEgressPolicy500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to apply egress policy"}}, nil } @@ -62,7 +61,7 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet // applyEgressPin brings Chromium's proxy pin in line with the policy. The // caller must hold chromiumConfigMu. // -// A pin that is missing, or was written for a different policy, is rewritten +// A pin that is missing, or is not the one this policy produces, is rewritten // by restarting Chromium rather than by waiting for it to reload its policy // directory, so the pin is in force before the control plane hands the session // to a client. Chromium does not need a restart for the pin to come off. @@ -71,25 +70,25 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet // restarting as the policy flips to unfiltered can write the pin again after it // is removed. That leaves the session pinned until Chromium next starts, which // fails closed. -func (s *ApiService) applyEgressPin(ctx context.Context, policy, previous egresspolicy.Policy) error { +func (s *ApiService) applyEgressPin(ctx context.Context, policy egresspolicy.Policy) error { if !policy.Filtered { return s.egressPin.Remove() } - present, err := s.egressPin.Present() + pinned, err := s.egressPin.Matches(policy, s.chromiumBaseFlags) if err != nil { return err } - if present && policy.Equal(previous) { + if pinned { return nil } if err := s.restartChromiumAndWait(ctx, "egress policy"); err != nil { return err } - present, err = s.egressPin.Present() + pinned, err = s.egressPin.Matches(policy, s.chromiumBaseFlags) if err != nil { return err } - if !present { + if !pinned { return errors.New("chromium restarted without the egress proxy pin") } return nil diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index 1bf2747e..e28e91a0 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -2,6 +2,7 @@ package api import ( "context" + "os" "testing" "github.com/kernel/kernel-images/server/lib/egresspolicy" @@ -30,7 +31,7 @@ func TestPutNetworkEgressPolicy(t *testing.T) { require.NoError(t, err) filtered := egresspolicy.Policy{Filtered: true} require.NoError(t, svc.egressPolicy.Set(filtered)) - require.NoError(t, svc.egressPin.Sync(filtered, []string{"--proxy-server=http://192.0.2.1:3129"})) + require.NoError(t, svc.egressPin.Sync(filtered, svc.chromiumBaseFlags)) for _, want := range []bool{true, true, false, false} { resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ @@ -39,7 +40,7 @@ func TestPutNetworkEgressPolicy(t *testing.T) { require.NoError(t, err) require.Equal(t, oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: want}, resp) require.Equal(t, want, svc.egressPolicy.Filtered()) - pinned, err := svc.egressPin.Present() + pinned, err := svc.egressPin.Matches(egresspolicy.Policy{Filtered: true}, svc.chromiumBaseFlags) require.NoError(t, err) require.Equal(t, want, pinned) } @@ -48,29 +49,29 @@ func TestPutNetworkEgressPolicy(t *testing.T) { // The pin's bypass list is the policy's private hosts, so a pin written for // other private hosts is rewritten with a restart, and one written for these // is left alone. The new hosts are recorded even when the restart fails, so the -// next Chromium start pins them. +// next Chromium start pins them, and a retry before that start still finds the +// old pin and restarts again rather than reporting it applied. func TestPutNetworkEgressPolicyRestartsForChangedPrivateHosts(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) before := egresspolicy.Policy{Filtered: true, PrivateHosts: &[]string{"10.1.0.0/16"}} require.NoError(t, svc.egressPolicy.Set(before)) - require.NoError(t, svc.egressPin.Sync(before, []string{"--proxy-server=http://192.0.2.1:3129"})) + require.NoError(t, svc.egressPin.Sync(before, svc.chromiumBaseFlags)) // Leaves supervisorctl off PATH, so a restart fails. t.Setenv("PATH", t.TempDir()) after := []string{"preview.internal:8443"} - resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ - Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &after}, - }) - require.NoError(t, err) - require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp, "changed private hosts did not restart Chromium") - require.Equal(t, &after, svc.egressPolicy.Policy().PrivateHosts) + put := oapi.PutNetworkEgressPolicyRequestObject{Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &after}} + for range 2 { + resp, err := svc.PutNetworkEgressPolicy(context.Background(), put) + require.NoError(t, err) + require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp, "a pin for other private hosts did not restart Chromium") + require.Equal(t, &after, svc.egressPolicy.Policy().PrivateHosts) + } // Stands in for the launcher, which writes the pin on the next start. - require.NoError(t, svc.egressPin.Sync(svc.egressPolicy.Policy(), []string{"--proxy-server=http://192.0.2.1:3129"})) - resp, err = svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ - Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &after}, - }) + require.NoError(t, svc.egressPin.Sync(svc.egressPolicy.Policy(), svc.chromiumBaseFlags)) + resp, err := svc.PutNetworkEgressPolicy(context.Background(), put) require.NoError(t, err) require.Equal(t, oapi.PutNetworkEgressPolicy200JSONResponse{Filtered: true, PrivateHosts: &after}, resp) } @@ -108,9 +109,8 @@ func TestPutNetworkEgressPolicyFailsWhenThePinCannotBeApplied(t *testing.T) { require.NoError(t, err) require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp) require.True(t, svc.egressPolicy.Filtered()) - pinned, err := svc.egressPin.Present() - require.NoError(t, err) - require.False(t, pinned) + _, err = os.Stat(svc.egressPin.Path) + require.ErrorIs(t, err, os.ErrNotExist) } func TestPutNetworkEgressPolicyRejectsMissingBody(t *testing.T) { diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index 372a20f2..c1b0996e 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -27,8 +27,6 @@ const ( // pulseSink is the null sink chromium plays into; the recorder captures // its .monitor source. pulseSink = "KernelOutput" - - defaultPrivateNetworkBypassFlag = "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7" ) func main() { @@ -94,15 +92,14 @@ func main() { fmt.Fprintf(os.Stderr, "egress is filtered; ignoring runtime flags that remap hosts: %s\n", strings.Join(dropped, " ")) } } - base := withDefaultPrivateNetworkBypass(chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, nil)) - if err := egresspolicy.DefaultPin.Sync(egress, base); err != nil { + if err := egresspolicy.DefaultPin.Sync(egress, egresspolicy.BaseFlags(baseFlags)); err != nil { fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) os.Exit(1) } final := chromiumflags.MergeFlagsWithRuntimeTokens(baseFlags, runtimeTokens) final = chromiumflags.TranslateKernelDisableFeatures(final) - final = withDefaultPrivateNetworkBypass(final) + final = egresspolicy.WithDefaultPrivateNetworkBypass(final) // Diagnostics for parity with previous scripts fmt.Printf("BASE_FLAGS: %s\n", baseFlags) @@ -182,15 +179,6 @@ func main() { } } -func withDefaultPrivateNetworkBypass(flags []string) []string { - for _, flag := range flags { - if flag == "--proxy-bypass-list" || strings.HasPrefix(flag, "--proxy-bypass-list=") { - return flags - } - } - return append(flags, defaultPrivateNetworkBypassFlag) -} - // execLookPath helps satisfy syscall.Exec's requirement to pass an absolute path. func execLookPath(file string) (string, error) { if strings.ContainsRune(file, os.PathSeparator) { diff --git a/server/cmd/chromium-launcher/main_test.go b/server/cmd/chromium-launcher/main_test.go index 6e6d11b0..7b4ba40a 100644 --- a/server/cmd/chromium-launcher/main_test.go +++ b/server/cmd/chromium-launcher/main_test.go @@ -4,69 +4,9 @@ import ( "os" "os/exec" "path/filepath" - "reflect" "testing" - - "github.com/kernel/kernel-images/server/lib/chromiumflags" ) -func TestWithDefaultPrivateNetworkBypass(t *testing.T) { - tests := []struct { - name string - flags []string - want []string - }{ - { - name: "image default", - want: []string{defaultPrivateNetworkBypassFlag}, - }, - { - name: "default follows unrelated flags", - flags: []string{"--kiosk"}, - want: []string{"--kiosk", defaultPrivateNetworkBypassFlag}, - }, - { - name: "custom list replaces image default", - flags: []string{"--proxy-bypass-list=preview.internal"}, - want: []string{"--proxy-bypass-list=preview.internal"}, - }, - { - name: "explicit empty list clears image default", - flags: []string{"--proxy-bypass-list="}, - want: []string{"--proxy-bypass-list="}, - }, - { - name: "bare empty list clears image default", - flags: []string{"--proxy-bypass-list"}, - want: []string{"--proxy-bypass-list"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := withDefaultPrivateNetworkBypass(tt.flags) - if !reflect.DeepEqual(got, tt.want) { - t.Fatalf("withDefaultPrivateNetworkBypass() mismatch:\n got: %#v\nwant: %#v", got, tt.want) - } - }) - } -} - -func TestDefaultPrivateNetworkBypassPreservesRuntimePrecedence(t *testing.T) { - configured := chromiumflags.MergeFlagsWithRuntimeTokens( - "--proxy-bypass-list=preview.internal", - []string{defaultPrivateNetworkBypassFlag}, - ) - got := withDefaultPrivateNetworkBypass(configured) - want := []string{ - "--proxy-bypass-list=preview.internal", - defaultPrivateNetworkBypassFlag, - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("runtime precedence changed:\n got: %#v\nwant: %#v", got, want) - } -} - func TestExecLookPath(t *testing.T) { dir := t.TempDir() bin := filepath.Join(dir, "mybin") diff --git a/server/lib/egresspolicy/egresspolicy.go b/server/lib/egresspolicy/egresspolicy.go index aaa97ef6..d4f95539 100644 --- a/server/lib/egresspolicy/egresspolicy.go +++ b/server/lib/egresspolicy/egresspolicy.go @@ -36,14 +36,6 @@ type Policy struct { PrivateHosts *[]string `json:"private_hosts,omitempty"` } -// Equal reports whether p and o are the same policy. -func (p Policy) Equal(o Policy) bool { - if p.Filtered != o.Filtered || (p.PrivateHosts == nil) != (o.PrivateHosts == nil) { - return false - } - return p.PrivateHosts == nil || slices.Equal(*p.PrivateHosts, *o.PrivateHosts) -} - // State is the session's egress policy as the control plane last reported it. // // It is held on disk as well as in memory. The API process is restarted diff --git a/server/lib/egresspolicy/egresspolicy_test.go b/server/lib/egresspolicy/egresspolicy_test.go index 8b4c35cc..17c43b90 100644 --- a/server/lib/egresspolicy/egresspolicy_test.go +++ b/server/lib/egresspolicy/egresspolicy_test.go @@ -5,6 +5,7 @@ import ( "log/slog" "os" "path/filepath" + "reflect" "testing" ) @@ -93,7 +94,7 @@ func TestPrivateHostsSurviveProcessRestart(t *testing.T) { if err := Load(path, silent()).Set(want); err != nil { t.Fatalf("Set(%+v): %v", want, err) } - if got := Load(path, silent()).Policy(); !got.Equal(want) { + if got := Load(path, silent()).Policy(); !reflect.DeepEqual(got, want) { t.Fatalf("policy after restart = %+v, want %+v", got, want) } } diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 927b59b3..6958e49f 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -1,6 +1,7 @@ package egresspolicy import ( + "bytes" "encoding/json" "errors" "fmt" @@ -9,6 +10,8 @@ import ( "slices" "strings" "syscall" + + "github.com/kernel/kernel-images/server/lib/chromiumflags" ) // DefaultPin is the pin as the image lays it out. Chromium merges every file @@ -90,9 +93,31 @@ func (p Pin) Sync(policy Policy, base []string) error { if !policy.Filtered { return p.Remove() } + data, err := encodePin(policy, base) + if err != nil { + return err + } + for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { + if err := ownDir(dir); err != nil { + return err + } + } + tmp := filepath.Join(p.StageDir, filepath.Base(p.Path)+".tmp") + defer os.Remove(tmp) + if err := writeSynced(tmp, data); err != nil { + return fmt.Errorf("write proxy pin: %w", err) + } + if err := os.Rename(tmp, p.Path); err != nil { + return fmt.Errorf("replace proxy pin: %w", err) + } + return nil +} + +// encodePin returns the pin Sync writes for a filtered policy. +func encodePin(policy Policy, base []string) ([]byte, error) { server, ok := lastFlagValue(base, "--proxy-server") if !ok || server == "" { - return errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") + return nil, errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") } var bypass string if policy.PrivateHosts != nil { @@ -107,22 +132,9 @@ func (p Pin) Sync(policy Policy, base []string) error { DnsOverHttpsMode: pinDnsOverHttpsMode, }) if err != nil { - return fmt.Errorf("encode proxy pin: %w", err) - } - for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { - if err := ownDir(dir); err != nil { - return err - } - } - tmp := filepath.Join(p.StageDir, filepath.Base(p.Path)+".tmp") - defer os.Remove(tmp) - if err := writeSynced(tmp, append(data, '\n')); err != nil { - return fmt.Errorf("write proxy pin: %w", err) - } - if err := os.Rename(tmp, p.Path); err != nil { - return fmt.Errorf("replace proxy pin: %w", err) + return nil, fmt.Errorf("encode proxy pin: %w", err) } - return nil + return append(data, '\n'), nil } // writeSynced writes data to path and flushes it to disk, so a crash after the @@ -146,6 +158,11 @@ func writeSynced(path string, data []byte) error { // hostMappingFlags rewrite the address Chromium connects to for a host, // including the egress proxy's own address, so a runtime rule could send every // request to a different proxy. Chromium honors them whatever the pin says. +// +// This is not every switch that touches the network. The token that writes +// runtime flags can also run processes in the VM, which egress without +// Chromium, so the list closes the routes found through Chromium's own +// configuration rather than defending against that token. var hostMappingFlags = []string{"host-resolver-rules", "host-rules"} // DropHostMappingFlags returns runtime flags without the ones that remap @@ -164,30 +181,26 @@ func DropHostMappingFlags(runtime []string) (kept, dropped []string) { return kept, dropped } -// Present reports whether a pin is in place. A file at the pin's path that is -// not a pin does not count, so the caller writes a real one over it. -func (p Pin) Present() (bool, error) { +// Matches reports whether the pin in place is exactly the one Sync writes for +// policy and base: none at all for an unfiltered policy. Anything else at the +// pin's path, including a pin written for other private hosts, does not match, +// so the caller has the real one written over it. +func (p Pin) Matches(policy Policy, base []string) (bool, error) { data, err := os.ReadFile(p.Path) if errors.Is(err, os.ErrNotExist) { - return false, nil + return !policy.Filtered, nil } if err != nil { return false, fmt.Errorf("read proxy pin: %w", err) } - var pinned struct { - ProxySettings proxySettings `json:"ProxySettings"` - WebRtcIPHandling string `json:"WebRtcIPHandling"` - WebRtcIPHandlingURL *[]json.RawMessage `json:"WebRtcIPHandlingUrl"` - DnsOverHttpsMode string `json:"DnsOverHttpsMode"` - } - if json.Unmarshal(data, &pinned) != nil { + if !policy.Filtered { return false, nil } - return pinned.ProxySettings.ProxyMode == pinProxyMode && - pinned.ProxySettings.ProxyServer != "" && - pinned.WebRtcIPHandling == pinWebRtcIPHandling && - pinned.WebRtcIPHandlingURL != nil && len(*pinned.WebRtcIPHandlingURL) == 0 && - pinned.DnsOverHttpsMode == pinDnsOverHttpsMode, nil + want, err := encodePin(policy, base) + if err != nil { + return false, err + } + return bytes.Equal(data, want), nil } // Remove deletes the pin. Chromium reloads its policy directory on its own, so @@ -218,6 +231,31 @@ func ownDir(dir string) error { return nil } +// DefaultPrivateNetworkBypassFlag is the image's bypass list, which sends +// private ranges around the proxy when the control plane sets no list of its +// own. +const DefaultPrivateNetworkBypassFlag = "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7" + +// WithDefaultPrivateNetworkBypass appends the image's bypass list unless flags +// already carry one. +func WithDefaultPrivateNetworkBypass(flags []string) []string { + for _, flag := range flags { + if flag == "--proxy-bypass-list" || strings.HasPrefix(flag, "--proxy-bypass-list=") { + return flags + } + } + return append(flags, DefaultPrivateNetworkBypassFlag) +} + +// BaseFlags returns the flags the pin is derived from: chromiumFlags, the +// CHROMIUM_FLAGS the control plane launches the VM with, before any runtime +// flags, plus the image's default bypass list. The launcher writes the pin from +// them and the egress policy handler checks it against them, so both must call +// this. +func BaseFlags(chromiumFlags string) []string { + return WithDefaultPrivateNetworkBypass(chromiumflags.MergeFlagsWithRuntimeTokens(chromiumFlags, nil)) +} + // lastFlagValue returns the value of the last occurrence of name, which is the // one Chromium uses. A bare flag has an empty value. func lastFlagValue(flags []string, name string) (string, bool) { diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index c312bea5..ee7086ba 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -2,10 +2,14 @@ package egresspolicy import ( "encoding/json" + "errors" "os" "path/filepath" + "reflect" "slices" "testing" + + "github.com/kernel/kernel-images/server/lib/chromiumflags" ) // testPin lays out a policy directory the way the image does, with the pin @@ -165,7 +169,7 @@ func TestPinSyncRefusesFilteredSessionWithoutProxy(t *testing.T) { if err := p.Sync(Policy{Filtered: true}, base); err == nil { t.Fatalf("Sync(filtered, %q) succeeded", base) } - if present, _ := p.Present(); present { + if _, err := os.Stat(p.Path); !errors.Is(err, os.ErrNotExist) { t.Fatalf("Sync(filtered, %q) wrote a pin", base) } } @@ -182,11 +186,11 @@ func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { if err := p.Sync(Policy{}, nil); err != nil { t.Fatalf("Sync(false): %v", err) } - present, err := p.Present() + unpinned, err := p.Matches(Policy{}, nil) if err != nil { - t.Fatalf("Present: %v", err) + t.Fatalf("Matches: %v", err) } - if present { + if !unpinned { t.Fatal("pin still present after the session became unfiltered") } } @@ -215,33 +219,105 @@ func TestPinSyncClosesThePolicyDirectories(t *testing.T) { } } -// A file at the pin's path that is not a pin must not stop the egress policy -// handler from restarting Chromium to write a real one. -func TestPinPresentRequiresAPin(t *testing.T) { +// A file at the pin's path that is not the pin for this policy must not stop +// the egress policy handler from restarting Chromium to write the real one. +// That includes a pin written for other private hosts, which is what a +// restart that failed before the launcher ran leaves behind. +func TestPinMatchesRequiresThePolicysPin(t *testing.T) { + base := []string{"--proxy-server=http://192.0.2.1:3129"} + policy := Policy{Filtered: true, PrivateHosts: &[]string{"preview.internal:8443"}} p := testPin(t) + if err := p.Sync(Policy{Filtered: true, PrivateHosts: &[]string{"10.1.0.0/16"}}, base); err != nil { + t.Fatalf("Sync: %v", err) + } + stale, err := os.ReadFile(p.Path) + if err != nil { + t.Fatalf("read pin: %v", err) + } for _, content := range []string{ "", "{}", + string(stale), `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp","DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","DnsOverHttpsMode":"off"}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, } { if err := os.WriteFile(p.Path, []byte(content), 0o644); err != nil { t.Fatalf("write %q: %v", content, err) } - present, err := p.Present() + pinned, err := p.Matches(policy, base) if err != nil { - t.Fatalf("Present(%q): %v", content, err) + t.Fatalf("Matches(%q): %v", content, err) } - if present { - t.Fatalf("Present(%q) reported a pin", content) + if pinned { + t.Fatalf("Matches(%q) accepted it as the pin", content) } } - if err := p.Sync(Policy{Filtered: true}, []string{"--proxy-server=http://192.0.2.1:3129"}); err != nil { + if err := p.Sync(policy, base); err != nil { t.Fatalf("Sync: %v", err) } - if present, err := p.Present(); err != nil || !present { - t.Fatalf("Present after Sync = %t, %v", present, err) + if pinned, err := p.Matches(policy, base); err != nil || !pinned { + t.Fatalf("Matches after Sync = %t, %v", pinned, err) + } + if unpinned, err := p.Matches(Policy{}, base); err != nil || unpinned { + t.Fatalf("Matches(unfiltered) with a pin in place = %t, %v", unpinned, err) + } +} + +func TestWithDefaultPrivateNetworkBypass(t *testing.T) { + tests := []struct { + name string + flags []string + want []string + }{ + { + name: "image default", + want: []string{DefaultPrivateNetworkBypassFlag}, + }, + { + name: "default follows unrelated flags", + flags: []string{"--kiosk"}, + want: []string{"--kiosk", DefaultPrivateNetworkBypassFlag}, + }, + { + name: "custom list replaces image default", + flags: []string{"--proxy-bypass-list=preview.internal"}, + want: []string{"--proxy-bypass-list=preview.internal"}, + }, + { + name: "explicit empty list clears image default", + flags: []string{"--proxy-bypass-list="}, + want: []string{"--proxy-bypass-list="}, + }, + { + name: "bare empty list clears image default", + flags: []string{"--proxy-bypass-list"}, + want: []string{"--proxy-bypass-list"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithDefaultPrivateNetworkBypass(tt.flags) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("WithDefaultPrivateNetworkBypass() mismatch:\n got: %#v\nwant: %#v", got, tt.want) + } + }) + } +} + +func TestDefaultPrivateNetworkBypassPreservesRuntimePrecedence(t *testing.T) { + configured := chromiumflags.MergeFlagsWithRuntimeTokens( + "--proxy-bypass-list=preview.internal", + []string{DefaultPrivateNetworkBypassFlag}, + ) + got := WithDefaultPrivateNetworkBypass(configured) + want := []string{ + "--proxy-bypass-list=preview.internal", + DefaultPrivateNetworkBypassFlag, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("runtime precedence changed:\n got: %#v\nwant: %#v", got, want) } } From 46bfbb7ce7c3f4ae2dbc983b6b8c24614dc80358 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:45:09 +0000 Subject: [PATCH 09/19] Test the egress pin's default bypass from the base flags The launcher and the egress policy handler both derive the pin from egresspolicy.BaseFlags, but no test covered a session without private hosts, where the bypass list comes from the base flags. Test BaseFlags directly, and apply the policy without private hosts first in the e2e test, which only succeeds if the launcher writes the pin the handler expects. --- server/e2e/e2e_egress_proxy_pin_test.go | 39 +++++++++++++++++-------- server/lib/egresspolicy/pin_test.go | 19 ++++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index ba6f290b..708d9572 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -43,6 +43,13 @@ func TestEgressProxyPin(t *testing.T) { client, err := c.APIClient() require.NoError(t, err) + // Without private hosts the pin keeps the image's default bypass. The + // request only succeeds if the pin the launcher writes is the one the + // handler expects. + putEgressPolicy(t, ctx, client, instanceoapi.NetworkEgressPolicy{Filtered: true}) + pin := readEgressPin(t, ctx, c) + require.Equal(t, strings.TrimPrefix(egresspolicy.DefaultPrivateNetworkBypassFlag, "--proxy-bypass-list="), pin.ProxySettings["ProxyBypassList"]) + putEgressPolicy(t, ctx, client, instanceoapi.NetworkEgressPolicy{ Filtered: true, PrivateHosts: &[]string{"10.1.0.0/16", "example.com:8443"}, @@ -69,15 +76,7 @@ func TestEgressProxyPin(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, flagsRsp.StatusCode(), "patch flags: %s", string(flagsRsp.Body)) - out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPin.Path}) - require.NoError(t, err, "read pin: %s", out) - var pin struct { - ProxySettings map[string]string `json:"ProxySettings"` - WebRtcIPHandling string `json:"WebRtcIPHandling"` - WebRtcIPHandlingURL []any `json:"WebRtcIPHandlingUrl"` - DnsOverHttpsMode string `json:"DnsOverHttpsMode"` - } - require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) + pin = readEgressPin(t, ctx, c) require.Equal(t, map[string]string{ "ProxyMode": "fixed_servers", "ProxyServer": "http://127.0.0.1:9", @@ -85,13 +84,13 @@ func TestEgressProxyPin(t *testing.T) { }, pin.ProxySettings) require.Equal(t, "disable_non_proxied_udp", pin.WebRtcIPHandling) - require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) - require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %s", out) + require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %+v", pin) + require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %+v", pin) require.Equal(t, "off", pin.DnsOverHttpsMode) // The flag's name is split so this script's own command line does not // match it. - out, err = execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", `! grep -qa -- "--host-resolver""-rules" /proc/[0-9]*/cmdline`}) + out, err := execCombinedOutputWithClient(ctx, c, "sh", []string{"-c", `! grep -qa -- "--host-resolver""-rules" /proc/[0-9]*/cmdline`}) require.NoError(t, err, "chromium started with a runtime host mapping rule on a filtered session: %s", out) // The user Chromium runs as must not be able to add a policy file that @@ -142,6 +141,22 @@ func TestEgressProxyPin(t *testing.T) { }, time.Minute, time.Second, "per-URL WebRTC rule still cleared after the session was unfiltered") } +type egressPin struct { + ProxySettings map[string]string `json:"ProxySettings"` + WebRtcIPHandling string `json:"WebRtcIPHandling"` + WebRtcIPHandlingURL []any `json:"WebRtcIPHandlingUrl"` + DnsOverHttpsMode string `json:"DnsOverHttpsMode"` +} + +func readEgressPin(t *testing.T, ctx context.Context, c *TestContainer) egressPin { + t.Helper() + out, err := execCombinedOutputWithClient(ctx, c, "cat", []string{egresspolicy.DefaultPin.Path}) + require.NoError(t, err, "read pin: %s", out) + var pin egressPin + require.NoError(t, json.Unmarshal([]byte(out), &pin), "decode pin: %s", out) + return pin +} + func putEgressPolicy(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, policy instanceoapi.NetworkEgressPolicy) { t.Helper() rsp, err := client.PutNetworkEgressPolicyWithResponse(ctx, policy) diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index ee7086ba..e552a3d0 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -111,6 +111,25 @@ func TestPinSyncFollowsBaseFlagsAndPolicy(t *testing.T) { } } +// The launcher and the egress policy handler both derive the pin from +// BaseFlags, so a session the control plane sends no private hosts for keeps +// the image's default bypass, or the bypass list in CHROMIUM_FLAGS. +func TestPinSyncFromBaseFlags(t *testing.T) { + const proxy = "--proxy-server=http://192.0.2.1:3129" + for chromiumFlags, want := range map[string]string{ + proxy: "10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7", + proxy + " --proxy-bypass-list=preview.internal": "preview.internal", + } { + p := testPin(t) + if err := p.Sync(Policy{Filtered: true}, BaseFlags(chromiumFlags)); err != nil { + t.Fatalf("Sync(%q): %v", chromiumFlags, err) + } + if got := readPin(t, p).ProxyBypassList; got != want { + t.Fatalf("bypass list pinned from %q = %q, want %q", chromiumFlags, got, want) + } + } +} + // Chromium resolves the egress proxy's own address through host mapping rules, // whatever the pin says, so a runtime rule could send every request to another // proxy. From a6eb7c925aec05c7949f23f9aa55c19d1c88e0b7 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:45:30 +0000 Subject: [PATCH 10/19] Test where the egress pin is written and staged Nothing checked that the image's pin sorts after policy.json in the same directory, or that it is staged outside that directory, where Chromium would apply the temporary file as policy. --- server/lib/egresspolicy/pin_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index e552a3d0..bbd36ec1 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -7,9 +7,11 @@ import ( "path/filepath" "reflect" "slices" + "strings" "testing" "github.com/kernel/kernel-images/server/lib/chromiumflags" + "github.com/kernel/kernel-images/server/lib/policy" ) // testPin lays out a policy directory the way the image does, with the pin @@ -53,6 +55,22 @@ func readPin(t *testing.T, p Pin) proxySettings { return pinned.ProxySettings } +// Chromium applies every file in its managed policy directory, and where two +// set the same policy the one that sorts last wins. The pin has to sit beside +// policy.json and sort after it, and be staged outside the directory. +func TestDefaultPinLayout(t *testing.T) { + dir := filepath.Dir(DefaultPin.Path) + if filepath.Dir(policy.PolicyPath) != dir { + t.Fatalf("pin %s is not beside %s", DefaultPin.Path, policy.PolicyPath) + } + if filepath.Base(DefaultPin.Path) <= filepath.Base(policy.PolicyPath) { + t.Fatalf("pin %s does not sort after %s", DefaultPin.Path, policy.PolicyPath) + } + if rel, err := filepath.Rel(dir, DefaultPin.StageDir); err != nil || !strings.HasPrefix(rel, "..") { + t.Fatalf("pin is staged in %s, inside the policy directory %s", DefaultPin.StageDir, dir) + } +} + // Once the pin is in place Chromium ignores the proxy flags entirely, so the // pin has to reproduce the egress proxy from the base flags, and the bypass list // from the private hosts the control plane sent, or from the base flags when it From 20e1536f4c09db451924f7214e0530d4671e69b3 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:45:39 +0000 Subject: [PATCH 11/19] Say the egress pin comes from the base flags and the policy The pin's bypass list has come from the policy's private hosts, not the flags Chromium is launched with, since the runtime bypass list stopped being pinned. --- server/lib/egresspolicy/pin.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 6958e49f..6cff6f4b 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -32,9 +32,10 @@ var DefaultPin = Pin{ // a CDP client can load such an extension with Extensions.loadUnpacked. Policy // is the one source an extension cannot override. // -// The pin is derived from the flags Chromium is launched with, so it is written -// by the launcher on every start. Once it is in place Chromium ignores -// --proxy-bypass-list as well as --proxy-server, which is why it carries both. +// The pin is derived from the base flags Chromium is launched with and the +// policy, and is written by the launcher on every start. Once it is in place +// Chromium ignores --proxy-bypass-list as well as --proxy-server, which is why +// it carries both. // // WebRTC sends UDP straight to the network rather than through an HTTP proxy, // so a page can reach any STUN or TURN server whatever the proxy says. The pin From c241c166be8fe4cd65c48db4fcd828b0ea0f6381 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:45:57 +0000 Subject: [PATCH 12/19] Reject every whitespace character in private_hosts The endpoint documents that entries may not contain whitespace, but only rejected spaces, tabs and line breaks. --- server/cmd/api/api/egress_policy.go | 3 ++- server/cmd/api/api/egress_policy_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index 0903c32e..b049ada7 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -4,6 +4,7 @@ import ( "context" "errors" "strings" + "unicode" "github.com/kernel/kernel-images/server/lib/egresspolicy" "github.com/kernel/kernel-images/server/lib/logger" @@ -30,7 +31,7 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet for _, host := range *req.Body.PrivateHosts { // Chromium splits its bypass list on both separators, so one entry // carrying either would add rules of its own. - if host == "" || strings.ContainsAny(host, ";, \t\r\n") { + if host == "" || strings.ContainsAny(host, ";,") || strings.ContainsFunc(host, unicode.IsSpace) { return oapi.PutNetworkEgressPolicy400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "private_hosts entries must be non-empty and contain no ';', ',' or whitespace"}}, nil } } diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index e28e91a0..bab85647 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -82,7 +82,7 @@ func TestPutNetworkEgressPolicyRejectsMalformedPrivateHosts(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) - for _, host := range []string{"", "a.internal;*", "a.internal,*", "a.internal *"} { + for _, host := range []string{"", "a.internal;*", "a.internal,*", "a.internal *", "a.internal\v*", "a.internal\u00a0*"} { hosts := []string{"10.1.0.0/16", host} resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ Body: &oapi.NetworkEgressPolicy{Filtered: true, PrivateHosts: &hosts}, From 5153187025bad254f6e61204bbc4d168bf673071 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 04:15:32 +0000 Subject: [PATCH 13/19] Keep chrome policy from merging per-URL WebRTC rules into the egress pin Chromium merges a list policy from every file that sets it, rather than taking the last, when PolicyListMultipleSourceMergeList names it. A chrome policy naming WebRtcIPHandlingUrl there added its rules to the pin's empty list and let a page send UDP around the egress proxy. The pin now sets that list to empty too. --- server/e2e/e2e_egress_proxy_pin_test.go | 8 ++++++-- server/lib/egresspolicy/pin.go | 7 ++++++- server/lib/egresspolicy/pin_test.go | 16 ++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/server/e2e/e2e_egress_proxy_pin_test.go b/server/e2e/e2e_egress_proxy_pin_test.go index 708d9572..ee1dae99 100644 --- a/server/e2e/e2e_egress_proxy_pin_test.go +++ b/server/e2e/e2e_egress_proxy_pin_test.go @@ -61,8 +61,9 @@ func TestEgressProxyPin(t *testing.T) { // clears per-URL WebRTC rules, turns DNS-over-HTTPS off, and leaves out // runtime flags that remap hosts. policyRsp, err := client.PatchChromiumPoliciesWithResponse(ctx, instanceoapi.PatchChromiumPoliciesJSONRequestBody{ - "WebRtcIPHandlingUrl": []map[string]string{{"url": "*", "handling": "default"}}, - "DnsOverHttpsMode": "secure", + "WebRtcIPHandlingUrl": []map[string]string{{"url": "*", "handling": "default"}}, + "PolicyListMultipleSourceMergeList": []string{"WebRtcIPHandlingUrl"}, + "DnsOverHttpsMode": "secure", }) require.NoError(t, err) require.Equal(t, http.StatusOK, policyRsp.StatusCode(), "patch policies: %s", string(policyRsp.Body)) @@ -87,6 +88,8 @@ func TestEgressProxyPin(t *testing.T) { require.NotNil(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %+v", pin) require.Empty(t, pin.WebRtcIPHandlingURL, "pin does not clear per-URL WebRTC rules: %+v", pin) require.Equal(t, "off", pin.DnsOverHttpsMode) + require.NotNil(t, pin.ListMergeList, "pin lets chrome policy merge per-URL WebRTC rules into its own: %+v", pin) + require.Empty(t, pin.ListMergeList, "pin lets chrome policy merge per-URL WebRTC rules into its own: %+v", pin) // The flag's name is split so this script's own command line does not // match it. @@ -146,6 +149,7 @@ type egressPin struct { WebRtcIPHandling string `json:"WebRtcIPHandling"` WebRtcIPHandlingURL []any `json:"WebRtcIPHandlingUrl"` DnsOverHttpsMode string `json:"DnsOverHttpsMode"` + ListMergeList []string `json:"PolicyListMultipleSourceMergeList"` } func readEgressPin(t *testing.T, ctx context.Context, c *TestContainer) egressPin { diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index 6cff6f4b..c4b21a35 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -42,7 +42,10 @@ var DefaultPin = Pin{ // also restricts WebRTC to connections that go through the proxy, and clears // the per-URL WebRtcIPHandlingUrl rules, which Chromium consults before // WebRtcIPHandling and which a chrome_policy override could otherwise use to -// turn direct UDP back on. +// turn direct UDP back on. A list policy that PolicyListMultipleSourceMergeList +// names is merged from every file that sets it rather than taken from the last, +// so the pin also empties that list, or an override naming WebRtcIPHandlingUrl +// would add its rules to the pin's. // // Chromium connects to a DNS-over-HTTPS server directly rather than through // the proxy, whenever it resolves a name itself, as it does for a bypassed @@ -62,6 +65,7 @@ type pinPolicy struct { WebRtcIPHandling string `json:"WebRtcIPHandling"` WebRtcIPHandlingURL []json.RawMessage `json:"WebRtcIPHandlingUrl"` DnsOverHttpsMode string `json:"DnsOverHttpsMode"` + ListMergeList []string `json:"PolicyListMultipleSourceMergeList"` } type proxySettings struct { @@ -131,6 +135,7 @@ func encodePin(policy Policy, base []string) ([]byte, error) { WebRtcIPHandling: pinWebRtcIPHandling, WebRtcIPHandlingURL: []json.RawMessage{}, DnsOverHttpsMode: pinDnsOverHttpsMode, + ListMergeList: []string{}, }) if err != nil { return nil, fmt.Errorf("encode proxy pin: %w", err) diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index bbd36ec1..0968ff68 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -36,12 +36,15 @@ func readPin(t *testing.T, p Pin) proxySettings { if err := json.Unmarshal(data, &policy); err != nil { t.Fatalf("decode pin: %v", err) } - if len(policy) != 4 { - t.Fatalf("pin sets %d policies, want ProxySettings, WebRtcIPHandling, WebRtcIPHandlingUrl and DnsOverHttpsMode: %s", len(policy), data) + if len(policy) != 5 { + t.Fatalf("pin sets %d policies, want ProxySettings, WebRtcIPHandling, WebRtcIPHandlingUrl, DnsOverHttpsMode and PolicyListMultipleSourceMergeList: %s", len(policy), data) } if string(policy["WebRtcIPHandlingUrl"]) != "[]" { t.Fatalf("pin leaves per-URL WebRTC rules in place: %s", data) } + if string(policy["PolicyListMultipleSourceMergeList"]) != "[]" { + t.Fatalf("pin lets chrome policy merge per-URL WebRTC rules into its own: %s", data) + } var pinned pinPolicy if err := json.Unmarshal(data, &pinned); err != nil { t.Fatalf("decode pin: %v", err) @@ -275,10 +278,11 @@ func TestPinMatchesRequiresThePolicysPin(t *testing.T) { "", "{}", string(stale), - `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","DnsOverHttpsMode":"off"}`, - `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[]}`, + `{"ProxySettings":{"ProxyMode":"direct"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off","PolicyListMultipleSourceMergeList":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"default","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off","PolicyListMultipleSourceMergeList":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","DnsOverHttpsMode":"off","PolicyListMultipleSourceMergeList":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"PolicyListMultipleSourceMergeList":[]}`, + `{"ProxySettings":{"ProxyMode":"fixed_servers","ProxyServer":"http://192.0.2.1:3129","ProxyBypassList":"preview.internal:8443"},"WebRtcIPHandling":"disable_non_proxied_udp","WebRtcIPHandlingUrl":[],"DnsOverHttpsMode":"off"}`, } { if err := os.WriteFile(p.Path, []byte(content), 0o644); err != nil { t.Fatalf("write %q: %v", content, err) From cd60e14ea924b95cf7c9db7d8a94f652809987f6 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 04:15:55 +0000 Subject: [PATCH 14/19] Test that the egress pin clears group and other write bits separately The directories were opened with mode 777, so the test passed when Sync only checked one of the two write bits before restricting them. --- server/lib/egresspolicy/pin_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/lib/egresspolicy/pin_test.go b/server/lib/egresspolicy/pin_test.go index 0968ff68..45c9c728 100644 --- a/server/lib/egresspolicy/pin_test.go +++ b/server/lib/egresspolicy/pin_test.go @@ -240,8 +240,10 @@ func TestPinSyncRemovesPinWhenUnfiltered(t *testing.T) { // add a policy file that sorts after the pin, or remove it. func TestPinSyncClosesThePolicyDirectories(t *testing.T) { p := testPin(t) - for _, dir := range []string{p.StageDir, filepath.Dir(p.Path)} { - if err := os.Chmod(dir, 0o777); err != nil { + // One open to the group only and one to others only, so each write bit + // has to be cleared on its own. + for dir, mode := range map[string]os.FileMode{p.StageDir: 0o775, filepath.Dir(p.Path): 0o757} { + if err := os.Chmod(dir, mode); err != nil { t.Fatalf("open %s: %v", dir, err) } } From c0c31f28a5c78ef4834ae911933a053b9fe57a98 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 04:16:11 +0000 Subject: [PATCH 15/19] Test that GET /network/egress-policy reports private hosts --- server/cmd/api/api/egress_policy_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index bab85647..ba805f51 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -21,6 +21,18 @@ func TestGetNetworkEgressPolicyDefaultsToUnfiltered(t *testing.T) { require.Equal(t, oapi.GetNetworkEgressPolicy200JSONResponse{Filtered: false}, resp) } +// The policy is reported as it was applied, private hosts included. +func TestGetNetworkEgressPolicyReportsPrivateHosts(t *testing.T) { + svc, err := newSvc(t, newMockRecordManager()) + require.NoError(t, err) + hosts := []string{"10.1.0.0/16"} + require.NoError(t, svc.egressPolicy.Set(egresspolicy.Policy{Filtered: true, PrivateHosts: &hosts})) + + resp, err := svc.GetNetworkEgressPolicy(context.Background(), oapi.GetNetworkEgressPolicyRequestObject{}) + require.NoError(t, err) + require.Equal(t, oapi.GetNetworkEgressPolicy200JSONResponse{Filtered: true, PrivateHosts: &hosts}, resp) +} + // The control plane applies the policy at setup and again whenever a running // session's allowlist changes, so the endpoint has to carry a session both ways // and tolerate being told the same thing twice. The session starts filtered From 829f3bcc6ea933a90c20912b638143263408fed2 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 05:00:03 +0000 Subject: [PATCH 16/19] Drop unreachable egress policy branches --- server/lib/egresspolicy/egresspolicy.go | 3 --- server/lib/egresspolicy/pin.go | 18 +++++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/server/lib/egresspolicy/egresspolicy.go b/server/lib/egresspolicy/egresspolicy.go index d4f95539..b0af6080 100644 --- a/server/lib/egresspolicy/egresspolicy.go +++ b/server/lib/egresspolicy/egresspolicy.go @@ -118,9 +118,6 @@ func cloneHosts(hosts *[]string) *[]string { return nil } c := slices.Clone(*hosts) - if c == nil { - c = []string{} - } return &c } diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index c4b21a35..bf9d2a64 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -120,15 +120,15 @@ func (p Pin) Sync(policy Policy, base []string) error { // encodePin returns the pin Sync writes for a filtered policy. func encodePin(policy Policy, base []string) ([]byte, error) { - server, ok := lastFlagValue(base, "--proxy-server") - if !ok || server == "" { + server := lastFlagValue(base, "--proxy-server") + if server == "" { return nil, errors.New("egress is filtered but Chromium's base flags have no --proxy-server to pin") } var bypass string if policy.PrivateHosts != nil { bypass = strings.Join(*policy.PrivateHosts, ";") } else { - bypass, _ = lastFlagValue(base, "--proxy-bypass-list") + bypass = lastFlagValue(base, "--proxy-bypass-list") } data, err := json.Marshal(pinPolicy{ ProxySettings: proxySettings{ProxyMode: pinProxyMode, ProxyServer: server, ProxyBypassList: bypass}, @@ -263,16 +263,16 @@ func BaseFlags(chromiumFlags string) []string { } // lastFlagValue returns the value of the last occurrence of name, which is the -// one Chromium uses. A bare flag has an empty value. -func lastFlagValue(flags []string, name string) (string, bool) { - value, found := "", false +// one Chromium uses. A missing or bare flag has an empty value. +func lastFlagValue(flags []string, name string) string { + var value string for _, flag := range flags { switch { case flag == name: - value, found = "", true + value = "" case strings.HasPrefix(flag, name+"="): - value, found = strings.TrimPrefix(flag, name+"="), true + value = strings.TrimPrefix(flag, name+"=") } } - return value, found + return value } From fe7d63fcde6dd8eaa75154526ef78e062fbc3442 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 05:01:04 +0000 Subject: [PATCH 17/19] Drop the egress pin failure test the changed-hosts test covers --- server/cmd/api/api/egress_policy_test.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/server/cmd/api/api/egress_policy_test.go b/server/cmd/api/api/egress_policy_test.go index ba805f51..d0e95087 100644 --- a/server/cmd/api/api/egress_policy_test.go +++ b/server/cmd/api/api/egress_policy_test.go @@ -2,7 +2,6 @@ package api import ( "context" - "os" "testing" "github.com/kernel/kernel-images/server/lib/egresspolicy" @@ -105,26 +104,6 @@ func TestPutNetworkEgressPolicyRejectsMalformedPrivateHosts(t *testing.T) { } } -// Without the pin, filtering means restarting Chromium so the launcher writes -// it. When that fails the caller is told so, and the session stays filtered: -// the next Chromium start pins the proxy, and the CDP proxy keeps refusing -// contexts with their own proxy in the meantime. -func TestPutNetworkEgressPolicyFailsWhenThePinCannotBeApplied(t *testing.T) { - svc, err := newSvc(t, newMockRecordManager()) - require.NoError(t, err) - // Leaves supervisorctl off PATH, so the restart fails. - t.Setenv("PATH", t.TempDir()) - - resp, err := svc.PutNetworkEgressPolicy(context.Background(), oapi.PutNetworkEgressPolicyRequestObject{ - Body: &oapi.NetworkEgressPolicy{Filtered: true}, - }) - require.NoError(t, err) - require.IsType(t, oapi.PutNetworkEgressPolicy500JSONResponse{}, resp) - require.True(t, svc.egressPolicy.Filtered()) - _, err = os.Stat(svc.egressPin.Path) - require.ErrorIs(t, err, os.ErrNotExist) -} - func TestPutNetworkEgressPolicyRejectsMissingBody(t *testing.T) { svc, err := newSvc(t, newMockRecordManager()) require.NoError(t, err) From 8bb76060614cae9b759bc7d6889bb9d5e03aebe5 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 05:10:59 +0000 Subject: [PATCH 18/19] Keep a nil private hosts slice recorded as an empty list --- server/lib/egresspolicy/egresspolicy.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/lib/egresspolicy/egresspolicy.go b/server/lib/egresspolicy/egresspolicy.go index b0af6080..d4f95539 100644 --- a/server/lib/egresspolicy/egresspolicy.go +++ b/server/lib/egresspolicy/egresspolicy.go @@ -118,6 +118,9 @@ func cloneHosts(hosts *[]string) *[]string { return nil } c := slices.Clone(*hosts) + if c == nil { + c = []string{} + } return &c } From ce5385afd6e53d9887a5e3010d85c46d4effa9e4 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Fri, 9 Oct 2026 05:58:19 +0000 Subject: [PATCH 19/19] Trim egress pin comments and say why Chromium is not starting When the pin cannot be written the launcher exits and supervisord restarts it, so the log line now says Chromium is not being started. --- server/cmd/api/api/api.go | 8 +- server/cmd/api/api/egress_policy.go | 28 +- server/cmd/chromium-launcher/main.go | 2 +- server/lib/egresspolicy/egresspolicy.go | 3 +- server/lib/egresspolicy/pin.go | 111 +- server/lib/oapi/oapi.go | 1389 +++++++++++------------ server/openapi.yaml | 26 +- 7 files changed, 754 insertions(+), 813 deletions(-) diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index 83c61c9a..d9d21dbd 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -141,12 +141,10 @@ type ApiService struct { // session's egress allowlist. Shared with the CDP proxy, which reads it to // decide whether to refuse a context that would set its own proxy. egressPolicy *egresspolicy.State - // egressPin is the managed policy that holds Chromium on the egress proxy - // while the session is filtered. The launcher writes it; the egress policy - // handler restarts Chromium to get it written and removes it. + // egressPin holds Chromium on the egress proxy while filtered. The + // launcher writes it; the egress policy handler checks and removes it. egressPin egresspolicy.Pin - // chromiumBaseFlags are the flags the launcher derives the pin from, so the - // handler can tell whether the pin in place is the one the policy needs. + // chromiumBaseFlags are the flags the launcher derives the pin from. chromiumBaseFlags []string lifecycleCtx context.Context diff --git a/server/cmd/api/api/egress_policy.go b/server/cmd/api/api/egress_policy.go index b049ada7..29c6ca66 100644 --- a/server/cmd/api/api/egress_policy.go +++ b/server/cmd/api/api/egress_policy.go @@ -18,10 +18,9 @@ func (s *ApiService) GetNetworkEgressPolicy(_ context.Context, _ oapi.GetNetwork } // PutNetworkEgressPolicy handles PUT /network/egress-policy. -// Records whether the session's egress is restricted to an allowlist, which is -// what the CDP proxy consults before forwarding a command that would put a -// browser context on its own proxy, and pins Chromium's proxy with managed -// policy so an extension cannot switch it to direct connections. +// Records whether the session's egress is restricted to an allowlist, which the +// CDP proxy consults before forwarding a command that would put a browser +// context on its own proxy, and pins Chromium's proxy with managed policy. func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNetworkEgressPolicyRequestObject) (oapi.PutNetworkEgressPolicyResponseObject, error) { log := logger.FromContext(ctx) if req.Body == nil { @@ -29,8 +28,7 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet } if req.Body.PrivateHosts != nil { for _, host := range *req.Body.PrivateHosts { - // Chromium splits its bypass list on both separators, so one entry - // carrying either would add rules of its own. + // Chromium splits its bypass list on these. if host == "" || strings.ContainsAny(host, ";,") || strings.ContainsFunc(host, unicode.IsSpace) { return oapi.PutNetworkEgressPolicy400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "private_hosts entries must be non-empty and contain no ';', ',' or whitespace"}}, nil } @@ -38,9 +36,7 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet } policy := egresspolicy.Policy{Filtered: req.Body.Filtered, PrivateHosts: req.Body.PrivateHosts} - // The pin is written by the launcher from the flags Chromium starts with, - // so applying it means a restart, which must not interleave with another - // configuration change. + // Applying the pin can restart Chromium. s.chromiumConfigMu.Lock() defer s.chromiumConfigMu.Unlock() @@ -62,15 +58,11 @@ func (s *ApiService) PutNetworkEgressPolicy(ctx context.Context, req oapi.PutNet // applyEgressPin brings Chromium's proxy pin in line with the policy. The // caller must hold chromiumConfigMu. // -// A pin that is missing, or is not the one this policy produces, is rewritten -// by restarting Chromium rather than by waiting for it to reload its policy -// directory, so the pin is in force before the control plane hands the session -// to a client. Chromium does not need a restart for the pin to come off. -// -// The launcher does not take chromiumConfigMu, so a Chromium that crashed and is -// restarting as the policy flips to unfiltered can write the pin again after it -// is removed. That leaves the session pinned until Chromium next starts, which -// fails closed. +// A missing or stale pin is written by restarting Chromium, so it is in force +// before the request returns; Chromium's own policy reload would be late. +// Removal needs no restart. A Chromium restarting on its own as the policy +// flips to unfiltered can write the pin back, which fails closed until its +// next start. func (s *ApiService) applyEgressPin(ctx context.Context, policy egresspolicy.Policy) error { if !policy.Filtered { return s.egressPin.Remove() diff --git a/server/cmd/chromium-launcher/main.go b/server/cmd/chromium-launcher/main.go index c1b0996e..843a13fb 100644 --- a/server/cmd/chromium-launcher/main.go +++ b/server/cmd/chromium-launcher/main.go @@ -93,7 +93,7 @@ func main() { } } if err := egresspolicy.DefaultPin.Sync(egress, egresspolicy.BaseFlags(baseFlags)); err != nil { - fmt.Fprintf(os.Stderr, "failed to sync egress proxy pin: %v\n", err) + fmt.Fprintf(os.Stderr, "not starting chromium: failed to sync egress proxy pin: %v\n", err) os.Exit(1) } diff --git a/server/lib/egresspolicy/egresspolicy.go b/server/lib/egresspolicy/egresspolicy.go index d4f95539..3f7247de 100644 --- a/server/lib/egresspolicy/egresspolicy.go +++ b/server/lib/egresspolicy/egresspolicy.go @@ -6,8 +6,7 @@ // destinations are allowed. The VM is told that an allowlist exists, because // some of what it can be asked to do would route traffic around that proxy, // and it cannot refuse those requests without knowing the session is filtered -// at all. It is also told the session's private hosts, the destinations that -// are meant to bypass the proxy, so that it can pin exactly those. +// at all. It is also told the session's private hosts, which bypass the proxy. package egresspolicy import ( diff --git a/server/lib/egresspolicy/pin.go b/server/lib/egresspolicy/pin.go index bf9d2a64..672874b3 100644 --- a/server/lib/egresspolicy/pin.go +++ b/server/lib/egresspolicy/pin.go @@ -14,49 +14,30 @@ import ( "github.com/kernel/kernel-images/server/lib/chromiumflags" ) -// DefaultPin is the pin as the image lays it out. Chromium merges every file -// in its managed policy directory, so the pin sits beside policy.json rather -// than inside it and never races the writers of that file. Where two files set -// the same policy, the one that sorts last wins, so the name keeps the pin -// ahead of policy.json. +// DefaultPin sits beside policy.json rather than inside it, so it never races +// that file's writers. Chromium merges every file in the directory and the one +// that sorts last wins, so the name keeps the pin ahead of policy.json. var DefaultPin = Pin{ Path: "/etc/chromium/policies/managed/zz-kernel-egress.json", StageDir: "/etc/chromium/policies", } -// Pin fixes Chromium's proxy with managed policy while the session is filtered. +// Pin fixes Chromium's network settings with managed policy while the session +// is filtered. Chromium applies proxy settings in the order policy, extensions, +// command line, so without it an extension can switch --proxy-server to direct. // -// Chromium is pointed at the egress proxy with --proxy-server, but it applies -// proxy settings in the order policy, extensions, command line. An extension -// with the proxy permission can therefore switch it to direct connections, and -// a CDP client can load such an extension with Extensions.loadUnpacked. Policy -// is the one source an extension cannot override. -// -// The pin is derived from the base flags Chromium is launched with and the -// policy, and is written by the launcher on every start. Once it is in place -// Chromium ignores --proxy-bypass-list as well as --proxy-server, which is why -// it carries both. -// -// WebRTC sends UDP straight to the network rather than through an HTTP proxy, -// so a page can reach any STUN or TURN server whatever the proxy says. The pin -// also restricts WebRTC to connections that go through the proxy, and clears -// the per-URL WebRtcIPHandlingUrl rules, which Chromium consults before -// WebRtcIPHandling and which a chrome_policy override could otherwise use to -// turn direct UDP back on. A list policy that PolicyListMultipleSourceMergeList -// names is merged from every file that sets it rather than taken from the last, -// so the pin also empties that list, or an override naming WebRtcIPHandlingUrl -// would add its rules to the pin's. -// -// Chromium connects to a DNS-over-HTTPS server directly rather than through -// the proxy, whenever it resolves a name itself, as it does for a bypassed -// hostname. The pin turns DNS-over-HTTPS off, so a chrome_policy override -// cannot name a server for it to connect to. +// Besides ProxySettings, the pin restricts WebRTC to proxied connections, since +// WebRTC sends UDP around an HTTP proxy, and turns DNS-over-HTTPS off, since +// Chromium connects to a DoH server directly. It empties WebRtcIPHandlingUrl, +// which Chromium consults before WebRtcIPHandling, and +// PolicyListMultipleSourceMergeList, which would otherwise merge another file's +// WebRtcIPHandlingUrl rules into the pin's. type Pin struct { // Path is the pin's file in Chromium's managed policy directory. Path string // StageDir is where the pin is written before it is renamed into place. - // It must be outside the policy directory, since Chromium applies every - // file there whatever its name, and on the same filesystem. + // Chromium applies every file in the policy directory whatever its name, + // so this must be outside it, on the same filesystem. StageDir string } @@ -81,19 +62,14 @@ const ( ) // Sync writes the pin when the session is filtered, and removes it when it is -// not. base is the flags the control plane launches the VM with, before any -// runtime flags are merged in. +// not. base is the flags from BaseFlags. // -// Nothing in the pin comes from runtime flags: anyone holding the session's -// token can write those, and a pinned runtime --proxy-server or -// --proxy-bypass-list would route traffic around the egress proxy. The proxy -// server is the base flags' own. The bypass list is the policy's private hosts, -// which only the control plane can set, or the base flags' bypass list when the -// control plane set none. +// Nothing in the pin comes from runtime flags, which anyone holding the +// session's token can write. The proxy server comes from base, and the bypass +// list from the policy's private hosts, or from base when the policy has none. // -// The policy directory, and the staging directory the pin is renamed from, -// are made the writer's own and closed to everyone else, so the user Chromium -// runs as cannot add a policy file that sorts after the pin or remove it. +// Both directories are made root-owned and closed to others, so the user +// Chromium runs as cannot add a policy file that sorts after the pin. func (p Pin) Sync(policy Policy, base []string) error { if !policy.Filtered { return p.Remove() @@ -143,8 +119,8 @@ func encodePin(policy Policy, base []string) ([]byte, error) { return append(data, '\n'), nil } -// writeSynced writes data to path and flushes it to disk, so a crash after the -// rename that follows cannot leave a torn pin in the policy directory. +// writeSynced writes data to path and flushes it, so a crash after the rename +// cannot leave a torn pin. func writeSynced(path string, data []byte) error { f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { @@ -161,20 +137,13 @@ func writeSynced(path string, data []byte) error { return f.Close() } -// hostMappingFlags rewrite the address Chromium connects to for a host, -// including the egress proxy's own address, so a runtime rule could send every -// request to a different proxy. Chromium honors them whatever the pin says. -// -// This is not every switch that touches the network. The token that writes -// runtime flags can also run processes in the VM, which egress without -// Chromium, so the list closes the routes found through Chromium's own -// configuration rather than defending against that token. +// hostMappingFlags can remap the egress proxy's own address, which Chromium +// honors whatever the pin says. var hostMappingFlags = []string{"host-resolver-rules", "host-rules"} -// DropHostMappingFlags returns runtime flags without the ones that remap -// hosts, and the flags it removed. The launcher applies it while the session -// is filtered. Switches are matched by name whatever their dash prefix, -// since Chromium accepts "-name" as well as "--name". +// DropHostMappingFlags splits runtime flags into the ones to keep and the +// host-mapping ones to drop while filtered. Chromium accepts "-name" as well +// as "--name", so any dash prefix matches. func DropHostMappingFlags(runtime []string) (kept, dropped []string) { for _, flag := range runtime { name, _, _ := strings.Cut(strings.TrimLeft(flag, "-"), "=") @@ -187,10 +156,8 @@ func DropHostMappingFlags(runtime []string) (kept, dropped []string) { return kept, dropped } -// Matches reports whether the pin in place is exactly the one Sync writes for -// policy and base: none at all for an unfiltered policy. Anything else at the -// pin's path, including a pin written for other private hosts, does not match, -// so the caller has the real one written over it. +// Matches reports whether the pin on disk is exactly what Sync writes for +// policy and base, or absent for an unfiltered policy. func (p Pin) Matches(policy Policy, base []string) (bool, error) { data, err := os.ReadFile(p.Path) if errors.Is(err, os.ErrNotExist) { @@ -209,8 +176,7 @@ func (p Pin) Matches(policy Policy, base []string) (bool, error) { return bytes.Equal(data, want), nil } -// Remove deletes the pin. Chromium reloads its policy directory on its own, so -// the proxy reverts to the command line without a restart. +// Remove deletes the pin. Chromium picks the removal up without a restart. func (p Pin) Remove() error { if err := os.Remove(p.Path); err != nil && !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("remove proxy pin: %w", err) @@ -237,9 +203,8 @@ func ownDir(dir string) error { return nil } -// DefaultPrivateNetworkBypassFlag is the image's bypass list, which sends -// private ranges around the proxy when the control plane sets no list of its -// own. +// DefaultPrivateNetworkBypassFlag is the image's bypass list, used when the +// control plane sets none. const DefaultPrivateNetworkBypassFlag = "--proxy-bypass-list=10.0.0.0/8;172.16.0.0/12;192.168.0.0/16;100.64.0.0/10;fc00::/7" // WithDefaultPrivateNetworkBypass appends the image's bypass list unless flags @@ -253,17 +218,15 @@ func WithDefaultPrivateNetworkBypass(flags []string) []string { return append(flags, DefaultPrivateNetworkBypassFlag) } -// BaseFlags returns the flags the pin is derived from: chromiumFlags, the -// CHROMIUM_FLAGS the control plane launches the VM with, before any runtime -// flags, plus the image's default bypass list. The launcher writes the pin from -// them and the egress policy handler checks it against them, so both must call -// this. +// BaseFlags returns CHROMIUM_FLAGS, without runtime flags, plus the image's +// default bypass list. The launcher writes the pin from these and the handler +// checks it against them, so both must call this. func BaseFlags(chromiumFlags string) []string { return WithDefaultPrivateNetworkBypass(chromiumflags.MergeFlagsWithRuntimeTokens(chromiumFlags, nil)) } -// lastFlagValue returns the value of the last occurrence of name, which is the -// one Chromium uses. A missing or bare flag has an empty value. +// lastFlagValue returns the value of name's last occurrence, which is the one +// Chromium uses. func lastFlagValue(flags []string, name string) string { var value string for _, flag := range flags { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index eb5bf2fd..b6cfb346 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6531,7 +6531,7 @@ type NetworkEgressPolicy struct { // Filtered Whether the session's egress is restricted to an allowlist. When true, the VM refuses requests that would reach destinations without going through the proxy that enforces it. Filtered bool `json:"filtered"` - // PrivateHosts The session's `network.private_hosts`, which bypass the egress proxy while the session is filtered. Omitted keeps the image's default bypass of private ranges; an empty list bypasses nothing. Entries are passed to Chromium's bypass list as validated by the control plane, and may not contain `;`, `,` or whitespace. + // PrivateHosts The session's `network.private_hosts`, which bypass the egress proxy while filtered. Omitted keeps the image's default bypass of private ranges; an empty list bypasses nothing. Entries may not contain `;`, `,` or whitespace. PrivateHosts *[]string `json:"private_hosts,omitempty"` } @@ -26642,349 +26642,349 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", @@ -26993,366 +26993,363 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 40c8d220..ff990938 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1752,19 +1752,12 @@ paths: answers it with a protocol error instead of forwarding it to Chromium. - With `filtered: true` the VM also pins Chromium's network settings - with managed policy, which neither extensions nor `chrome_policy` - overrides can change: `ProxySettings` holds Chromium on the proxy in - its base flags, with `private_hosts` as the bypass list; - `WebRtcIPHandling` and an empty `WebRtcIPHandlingUrl` keep WebRTC - from sending UDP around the proxy; and `DnsOverHttpsMode` is `off`. - While filtered, the proxy and bypass list come only from the base - flags and this request, and runtime flags that remap hosts - (`--host-resolver-rules`, `--host-rules`) are ignored. - If the pin is not already in place for this policy, Chromium is - restarted to apply it before the request returns. With - `filtered: false` the pin is removed without a restart, and - Chromium stops applying it on its next policy reload. + With `filtered: true` the VM also pins Chromium's proxy, WebRTC and + DNS-over-HTTPS settings with managed policy, using the proxy from the + base flags and `private_hosts` as the bypass list. While filtered, + runtime `--host-resolver-rules` and `--host-rules` are ignored. + Chromium restarts if the pin is not already in place for this + policy. With `filtered: false` the pin is removed without a restart. Idempotent, and applied by the control plane both when the session is @@ -6618,10 +6611,9 @@ components: type: string description: >- The session's `network.private_hosts`, which bypass the egress - proxy while the session is filtered. Omitted keeps the image's - default bypass of private ranges; an empty list bypasses nothing. - Entries are passed to Chromium's bypass list as validated by the - control plane, and may not contain `;`, `,` or whitespace. + proxy while filtered. Omitted keeps the image's default bypass of + private ranges; an empty list bypasses nothing. Entries may not + contain `;`, `,` or whitespace. additionalProperties: false TelemetryState: type: object