Repository navigation
Feat: Per-VM socket handover and control channel for MOCA SBX #1569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
57a44a6
feat: per-VM Unix-socket listeners handed over at run time, with a co…
mrsabath fc59b25
Merge branch 'main' into fix/sbx-vm-control
mrsabath 875fced
fix: bind TLS-bridged requests to their VM, and close the hook race (…
mrsabath da228cb
fix: File pre-hydration refusals under the VM's own session (#1477)
mrsabath dc2528b
test: Poll for the bridged request's audit line instead of racing it …
mrsabath 853a35d
fix: File opaque VM tunnel rows under the VM's own session (#1477)
mrsabath 15974a7
Merge branch 'main' into fix/sbx-vm-control
mrsabath File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| // The MOCA SBX VM control channel (#1477), configured env-only: the conformance | ||
| // harness drives it through MOCA_EGRESS_CONTROL_SOCK / _UIDS, and operator-facing | ||
| // YAML fields follow once there is an operator to configure them. Off unless | ||
| // MOCA_EGRESS_CONTROL_SOCK names a path, so a proxy with no MOCA around it keeps | ||
| // today's behavior byte for byte. | ||
|
|
||
| const ( | ||
| envTenancy = "MOCA_TENANCY" | ||
| envControlSock = "MOCA_EGRESS_CONTROL_SOCK" | ||
| envControlUIDs = "MOCA_EGRESS_CONTROL_UIDS" | ||
| ) | ||
|
|
||
| // controlConfig is the resolved VM control channel; a zero Socket means off. | ||
| type controlConfig struct { | ||
| Socket string | ||
| AllowedUIDs []int | ||
| } | ||
|
|
||
| // vmControlServer is the subset of the control server main needs — closing on | ||
| // shutdown, which unregisters every VM and waits for their aborted audit lines. | ||
| // An interface so the non-unix stub can exist without the package. | ||
| type vmControlServer interface{ Close() error } | ||
|
|
||
| // resolveControlConfig reads the VM control channel's environment through | ||
| // getenv. Every error names its variable; an unset MOCA_EGRESS_CONTROL_SOCK | ||
| // turns the whole channel off without looking at anything else, so the | ||
| // tenancy check only fires where MOCA actually deployed this proxy. | ||
| func resolveControlConfig(getenv func(string) string) (controlConfig, error) { | ||
| var cc controlConfig | ||
| cc.Socket = strings.TrimSpace(getenv(envControlSock)) | ||
| if cc.Socket == "" { | ||
| return cc, nil // feature off | ||
| } | ||
| // Tenancy is decided once, here, not per request: a Register carrying a | ||
| // grant is refused by the registry with the same answer (MI1 S2). | ||
| switch t := getenv(envTenancy); t { | ||
| case "", "single": | ||
| case "multi": | ||
| return cc, fmt.Errorf("%s=multi: single-tenant only (MI1 S2)", envTenancy) | ||
| default: | ||
| return cc, fmt.Errorf("%s=%q: want unset or \"single\" (MI1 S2)", envTenancy, t) | ||
| } | ||
| // No default uid list: a control socket that admits nobody is a | ||
| // misconfiguration, and one that admits everybody would be worse. | ||
| raw := strings.TrimSpace(getenv(envControlUIDs)) | ||
| if raw == "" { | ||
| return cc, fmt.Errorf("%s is required when %s is set", envControlUIDs, envControlSock) | ||
| } | ||
| for _, item := range strings.Split(raw, ",") { | ||
| item = strings.TrimSpace(item) | ||
| uid, err := strconv.Atoi(item) | ||
| if err != nil || uid < 0 { | ||
| return cc, fmt.Errorf("%s: %q is not a non-negative integer", envControlUIDs, item) | ||
| } | ||
| cc.AllowedUIDs = append(cc.AllowedUIDs, uid) | ||
| } | ||
| return cc, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| //go:build !unix | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "github.com/rossoctl/cortex/core/listener/forwardproxy" | ||
| ) | ||
|
|
||
| // startVMControl refuses: the per-VM socket handover needs SCM_RIGHTS and peer | ||
| // credentials, so a configured channel on a non-Unix platform fails closed | ||
| // rather than running silently without it. | ||
| func startVMControl(cfg controlConfig, fp *forwardproxy.Server) vmControlServer { | ||
| fatalf("vm control: %s: per-VM socket handover requires a Unix platform (SCM_RIGHTS)", envControlSock) | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestResolveControlConfig(t *testing.T) { | ||
| env := func(kv ...string) func(string) string { | ||
| m := map[string]string{} | ||
| for i := 0; i+1 < len(kv); i += 2 { | ||
| m[kv[i]] = kv[i+1] | ||
| } | ||
| return func(k string) string { return m[k] } | ||
| } | ||
|
|
||
| cases := []struct { | ||
| name string | ||
| getenv func(string) string | ||
| wantErr string // "" = no error; matched as a substring | ||
| wantOn bool | ||
| }{ | ||
| { | ||
| name: "unset socket: off, tenancy not even read", | ||
| getenv: env(envTenancy, "multi"), | ||
| }, | ||
| { | ||
| name: "single tenancy", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "501", envTenancy, "single"), | ||
| wantOn: true, | ||
| }, | ||
| { | ||
| name: "tenancy unset", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "0"), | ||
| wantOn: true, | ||
| }, | ||
| { | ||
| name: "multi tenancy is a boot failure naming the variable", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "0", envTenancy, "multi"), | ||
| wantErr: envTenancy, | ||
| }, | ||
| { | ||
| name: "unknown tenancy value names the variable", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "0", envTenancy, "dual"), | ||
| wantErr: envTenancy, | ||
| }, | ||
| { | ||
| name: "socket without uids is a misconfiguration", | ||
| getenv: env(envControlSock, "/run/c.sock"), | ||
| wantErr: envControlUIDs, | ||
| }, | ||
| { | ||
| name: "a non-integer uid names the variable", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "0,root"), | ||
| wantErr: envControlUIDs, | ||
| }, | ||
| { | ||
| name: "a negative uid is refused", | ||
| getenv: env(envControlSock, "/run/c.sock", envControlUIDs, "-1"), | ||
| wantErr: envControlUIDs, | ||
| }, | ||
| } | ||
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| cc, err := resolveControlConfig(tc.getenv) | ||
| if tc.wantErr == "" { | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| if (cc.Socket != "") != tc.wantOn { | ||
| t.Errorf("on = %v (socket %q), want %v", cc.Socket != "", cc.Socket, tc.wantOn) | ||
| } | ||
| return | ||
| } | ||
| if err == nil { | ||
| t.Fatalf("want an error naming %s, got config %+v", tc.wantErr, cc) | ||
| } | ||
| if !strings.Contains(err.Error(), tc.wantErr) { | ||
| t.Errorf("error %q does not name %s", err, tc.wantErr) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // The uid list parses comma-separated with whitespace. | ||
| cc, err := resolveControlConfig(env(envControlSock, "/run/c.sock", envControlUIDs, "0, 501 ,2")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if len(cc.AllowedUIDs) != 3 || cc.AllowedUIDs[0] != 0 || cc.AllowedUIDs[1] != 501 || cc.AllowedUIDs[2] != 2 { | ||
| t.Errorf("uids = %v, want [0 501 2]", cc.AllowedUIDs) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| //go:build unix | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "log/slog" | ||
| "os" | ||
|
|
||
| "github.com/rossoctl/cortex/core/listener/forwardproxy" | ||
| "github.com/rossoctl/cortex/core/listener/vmcontrol" | ||
| ) | ||
|
|
||
| // startVMControl brings up the VM control channel: the control socket vmpool | ||
| // hands per-VM egress listeners over, a Registry binding each to fp, and the | ||
| // trusted-session hook that makes which-socket-accepted-the-connection the | ||
| // session identity for everything a VM sends. | ||
| func startVMControl(cfg controlConfig, fp *forwardproxy.Server) vmControlServer { | ||
| // The listener's own answer, never the request's: see TrustedSessionID. | ||
| fp.TrustedSessionID = vmcontrol.SessionIDFromRequest | ||
| // A TLS-bridged tunnel's decrypted requests are parsed fresh and bypass the | ||
| // per-VM wrapper, so the binding has to be re-stamped onto each of them or | ||
| // the guest's own session headers decide. See AdoptBridgedRequest. | ||
| fp.AdoptBridgedRequest = vmcontrol.AdoptBridgedRequest | ||
| reg := vmcontrol.NewRegistry(fp, vmcontrol.NewJSONSink(os.Stdout)) | ||
| srv, err := vmcontrol.NewServer(cfg.Socket, cfg.AllowedUIDs, reg) | ||
| if err != nil { | ||
| fatalf("vm control: %v", err) | ||
| } | ||
| go func() { | ||
| if err := srv.Serve(); err != nil { | ||
| fatalf("vm control: %v", err) | ||
| } | ||
| }() | ||
| slog.Info("vm control listening — MOCA SBX per-VM egress sockets", | ||
| "socket", cfg.Socket, "allowed_uids", cfg.AllowedUIDs) | ||
| return srv | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: rossoctl/cortex
Length of output: 5290
Script executed:
Repository: rossoctl/cortex
Length of output: 42580
Script executed:
Repository: rossoctl/cortex
Length of output: 41515
Make VM-control shutdown cancel in-flight CONNECT dials.
vmCtrl.Close()can wait for an in-flight CONNECT handler. The handler callsnet.DialTimeoutwith a fixed 10-second timeout, so closing the tracked client connection does not cancel the upstream dial. This exceeds the one-second local shutdown budget and can prevent the final audit and store flushes before the three-second supervisor grace period ends.Use a context canceled by VM unregistration for the CONNECT dial, and make VM-control shutdown honor
shutdownCtx. Preserve the terminal aborted audit emission when cancellation stops the handler.