From 1505a179672871b00abccbc0b3e43689cb24c814 Mon Sep 17 00:00:00 2001 From: chou-godaddy Date: Thu, 5 Nov 2026 10:58:07 -0700 Subject: [PATCH] fix(hydrator): read sandbox identity from device.container OpenShell after v0.1.7 (NVIDIA/OpenShell#5048) moves supervisor sandbox identity from the top-level container object to device.container. The hydrator read only the top-level field, so every record from an upgraded supervisor was unmapped. Prefer device.container and fall back to the top-level container so mixed fleets keep attributing during upgrades. Fixes #23 Signed-off-by: chou-godaddy --- deploy/examples/hydrator.yaml | 4 +- internal/hydrator/activity.go | 9 +- internal/hydrator/activity_test.go | 42 +++++++ internal/hydrator/agentmap.go | 15 +-- internal/hydrator/agentmap_test.go | 8 +- internal/hydrator/aggregate.go | 2 +- internal/hydrator/doc.go | 9 +- internal/hydrator/event.go | 23 +++- internal/hydrator/event_test.go | 39 +++++++ internal/hydrator/fixtures_test.go | 30 +++++ internal/hydrator/run.go | 2 +- internal/hydrator/run_test.go | 105 ++++++++++++++++++ .../supervisor-device-container.jsonl | 14 +++ 13 files changed, 281 insertions(+), 21 deletions(-) create mode 100644 internal/hydrator/testdata/supervisor-device-container.jsonl diff --git a/deploy/examples/hydrator.yaml b/deploy/examples/hydrator.yaml index f9df029..0a93520 100644 --- a/deploy/examples/hydrator.yaml +++ b/deploy/examples/hydrator.yaml @@ -19,5 +19,5 @@ signing: # optional; signs each agents: - agent_id: 7a4b2e91-83f6-4c12-9d58-bf1e6a3c9d07 ans_name: ans://v1.0.0.payments.example.com - sandbox_names: [payments] # container.name in the OCSF record - sandbox_uids: [] # container.uid, the durable association + sandbox_names: [payments] # device.container.name (container.name up to OpenShell v0.1.7) + sandbox_uids: [] # device.container.uid, the durable association diff --git a/internal/hydrator/activity.go b/internal/hydrator/activity.go index 737f61f..9fd6c92 100644 --- a/internal/hydrator/activity.go +++ b/internal/hydrator/activity.go @@ -439,13 +439,14 @@ func (s *activityStats) policyChanged(policies []PolicyContext) bool { } func activitySandbox(ev Event) string { - if ev.Container == nil { + sandbox := ev.Sandbox() + if sandbox == nil { return "" } - if ev.Container.UID != "" { - return ev.Container.UID + if sandbox.UID != "" { + return sandbox.UID } - return ev.Container.Name + return sandbox.Name } func boundedActivityText(s string) string { diff --git a/internal/hydrator/activity_test.go b/internal/hydrator/activity_test.go index 820bf60..72dccb4 100644 --- a/internal/hydrator/activity_test.go +++ b/internal/hydrator/activity_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "strconv" "strings" "testing" @@ -228,6 +229,47 @@ func TestActivityPolicyBeforeWindow(t *testing.T) { } } +func TestActivityDeviceContainerShape(t *testing.T) { + t.Parallel() + window := Window{Start: time.UnixMilli(sampleBaseMs).UTC(), End: time.UnixMilli(sampleBaseMs + 60_000).UTC()} + policy := func(uid string, offsetMs int64) string { + return jsonLine(t, map[string]any{"class_uid": ClassDeviceConfigChange, "activity_id": 1, "time": sampleBaseMs + offsetMs, "container": map[string]any{"uid": sandboxAUID}, "metadata": map[string]any{"uid": uid}, "unmapped": map[string]any{"policy_hash": "hash-" + uid, "version": 1}}) + } + original := strings.Join([]string{ + policy("p1", -1000), + activityInput(t, "a1", "L7_REQUEST allow GET example.com:443/test reason=rule", 4002, 99, 1), + activityInput(t, "a2", "L7_REQUEST deny POST example.com:443/test reason=rule", 4002, 99, 2), + policy("p2", 1000), + }, "\n") + run := func(t *testing.T, input string) (Report, string) { + t.Helper() + opts := activityOptions(t) + opts.Window = &window + var evidence bytes.Buffer + opts.Evidence = &evidence + report, err := Run(t.Context(), strings.NewReader(input), &fakeSink{}, opts, nil) + if err != nil { + t.Fatal(err) + } + return report, evidence.String() + } + wantReport, wantEvidence := run(t, original) + if len(wantReport.Activity) != 1 || len(wantReport.Activity[0].Policies) != 2 || wantReport.OutOfWindow != 1 { + t.Fatalf("baseline activity=%+v outOfWindow=%d, want one agent with both policies", wantReport.Activity, wantReport.OutOfWindow) + } + input := moveSandboxToDevice(t, original, everyLine) + if got := strings.Count(input, `"device":{"container"`); got != 4 { + t.Fatalf("moved %d records, want 4", got) + } + report, evidence := run(t, input) + if !reflect.DeepEqual(report, wantReport) { + t.Fatalf("Report = %+v\nwant %+v", report, wantReport) + } + if evidence != wantEvidence { + t.Fatalf("evidence differs:\n%s\nwant\n%s", evidence, wantEvidence) + } +} + func TestActivityFailureStatusKeepsLegacyBehavior(t *testing.T) { t.Parallel() for _, tc := range []struct { diff --git a/internal/hydrator/agentmap.go b/internal/hydrator/agentmap.go index 5f4fdf9..0dd366e 100644 --- a/internal/hydrator/agentmap.go +++ b/internal/hydrator/agentmap.go @@ -50,19 +50,20 @@ func NewAgentMap(mappings []AgentMapping) (AgentMap, error) { return m, nil } -// Lookup resolves the event's sandbox to an agent, by container.uid first and -// container.name second. Events without a container never resolve. +// Lookup resolves the event's sandbox (see Event.Sandbox) to an agent, by uid +// first and name second. Events without a sandbox never resolve. func (m AgentMap) Lookup(ev Event) (AgentMapping, bool) { - if ev.Container == nil { + sandbox := ev.Sandbox() + if sandbox == nil { return AgentMapping{}, false } - if ev.Container.UID != "" { - if mapping, ok := m.byUID[ev.Container.UID]; ok { + if sandbox.UID != "" { + if mapping, ok := m.byUID[sandbox.UID]; ok { return mapping, true } } - if ev.Container.Name != "" { - if mapping, ok := m.byName[ev.Container.Name]; ok { + if sandbox.Name != "" { + if mapping, ok := m.byName[sandbox.Name]; ok { return mapping, true } } diff --git a/internal/hydrator/agentmap_test.go b/internal/hydrator/agentmap_test.go index a9829ae..73927cd 100644 --- a/internal/hydrator/agentmap_test.go +++ b/internal/hydrator/agentmap_test.go @@ -73,8 +73,14 @@ func TestAgentMapLookup(t *testing.T) { name string m AgentMap container *Container + device *Device wantID string }{ + {name: "by device container uid", m: m, device: &Device{Container: &Container{UID: "u-a"}}, wantID: "a"}, + {name: "by device container name", m: m, device: &Device{Container: &Container{Name: "n-b"}}, wantID: "b"}, + {name: "device container wins over top-level", m: m, device: &Device{Container: &Container{UID: "u-a"}}, container: &Container{Name: "n-b"}, wantID: "a"}, + {name: "unknown device container", m: m, device: &Device{Container: &Container{UID: "x"}}, container: &Container{Name: "n-b"}}, + {name: "device without container", m: m, device: &Device{}, container: &Container{Name: "n-b"}, wantID: "b"}, {name: "by uid", m: m, container: &Container{UID: "u-a", Name: "renamed"}, wantID: "a"}, {name: "by name when uid unknown", m: m, container: &Container{UID: "other", Name: "n-a"}, wantID: "a"}, {name: "uid wins over another agent's name", m: m, container: &Container{UID: "u-a", Name: "n-b"}, wantID: "a"}, @@ -87,7 +93,7 @@ func TestAgentMapLookup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, ok := tt.m.Lookup(Event{Container: tt.container}) + got, ok := tt.m.Lookup(Event{Device: tt.device, Container: tt.container}) if ok != (tt.wantID != "") { t.Fatalf("Lookup() ok = %v, want %v", ok, tt.wantID != "") } diff --git a/internal/hydrator/aggregate.go b/internal/hydrator/aggregate.go index c94596a..cd28163 100644 --- a/internal/hydrator/aggregate.go +++ b/internal/hydrator/aggregate.go @@ -142,7 +142,7 @@ func (a *attributor) add(ev Event) { mapping, ok := a.agents.Lookup(ev) if !ok { a.unmapped++ - if ev.Container == nil { + if ev.Sandbox() == nil { a.noContainer++ } return diff --git a/internal/hydrator/doc.go b/internal/hydrator/doc.go index dcb4b6b..0bbe9ff 100644 --- a/internal/hydrator/doc.go +++ b/internal/hydrator/doc.go @@ -8,10 +8,11 @@ // MaxLineBytes, unknown fields are ignored, missing fields are tolerated and a // malformed line is counted and skipped, never fatal. // -// Events are attributed to agents through their sandbox (container.uid, then -// container.name). Records that OpenShell downgraded to OCSF 1.1 or 1.3 have -// the container stripped and therefore cannot be attributed; they are counted -// as unmapped. +// Events are attributed to agents through their sandbox (uid, then name), +// read from device.container and, for OpenShell v0.1.7 and earlier, from the +// top-level container. Records without either, such as those an earlier +// release downgraded to OCSF 1.1 or 1.3, cannot be attributed; they are +// counted as unmapped. // // Per agent and input window the package aggregates Network Activity (4001) // and HTTP Activity (4002) events into two observations. An event counts as diff --git a/internal/hydrator/event.go b/internal/hydrator/event.go index 4fc8c9d..2e1f3cd 100644 --- a/internal/hydrator/event.go +++ b/internal/hydrator/event.go @@ -49,6 +49,7 @@ type Event struct { Message string `json:"message"` Time int64 `json:"time"` Metadata Metadata `json:"metadata"` + Device *Device `json:"device"` Container *Container `json:"container"` DstEndpoint *Endpoint `json:"dst_endpoint"` Actor *Actor `json:"actor"` @@ -73,8 +74,16 @@ type Product struct { Version string `json:"version"` } +// Device is the OCSF device object. Only its container is read: the device's +// own uid, name and hostname describe where the emitter runs, never which +// sandbox an event belongs to. +type Device struct { + Container *Container `json:"container"` +} + // Container is the sandbox the event belongs to. OpenShell omits it for -// events without a sandbox association and strips it on schema downgrade. +// events without a sandbox association; releases before v0.1.7 also stripped +// it on schema downgrade. type Container struct { UID string `json:"uid"` Name string `json:"name"` @@ -113,6 +122,18 @@ func (e Event) Timestamp() (time.Time, bool) { return time.UnixMilli(e.Time).UTC(), true } +// Sandbox returns the sandbox the event belongs to, or nil when it has none. +// OpenShell releases after v0.1.7 carry it in device.container and earlier +// releases in the top-level container. A device.container with neither uid +// nor name is treated as absent so it cannot hide a usable top-level one. +func (e Event) Sandbox() *Container { + if e.Device != nil && e.Device.Container != nil && + (e.Device.Container.UID != "" || e.Device.Container.Name != "") { + return e.Device.Container + } + return e.Container +} + // Denied reports whether the event was denied, blocked or failed: any of // action_id, disposition_id or status_id equals 2. func (e Event) Denied() bool { diff --git a/internal/hydrator/event_test.go b/internal/hydrator/event_test.go index 941a4b9..4f1610e 100644 --- a/internal/hydrator/event_test.go +++ b/internal/hydrator/event_test.go @@ -315,6 +315,45 @@ func TestEventDenied(t *testing.T) { } } +func TestEventSandbox(t *testing.T) { + t.Parallel() + tests := []struct { + name string + record string + wantUID string + wantName string + }{ + {name: "no sandbox", record: `{"class_uid":4001}`}, + {name: "top-level container", record: `{"container":{"uid":"top","name":"top-name"}}`, wantUID: "top", wantName: "top-name"}, + {name: "device container", record: `{"device":{"hostname":"h","container":{"uid":"dev","name":"dev-name"}}}`, wantUID: "dev", wantName: "dev-name"}, + {name: "device container name only", record: `{"device":{"container":{"name":"dev-name"}}}`, wantName: "dev-name"}, + {name: "device container wins", record: `{"device":{"container":{"uid":"dev"}},"container":{"uid":"top"}}`, wantUID: "dev"}, + {name: "device without container falls back", record: `{"device":{"hostname":"h","type_id":99},"container":{"uid":"top"}}`, wantUID: "top"}, + {name: "empty device container falls back", record: `{"device":{"container":{}},"container":{"uid":"top"}}`, wantUID: "top"}, + {name: "empty device container alone", record: `{"device":{"container":{}}}`}, + {name: "device identity is not the sandbox", record: `{"device":{"uid":"gateway-a","name":"gateway-a","hostname":"h"}}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ev, ok := decodeLine([]byte(tt.record)) + if !ok { + t.Fatalf("decodeLine(%s) failed", tt.record) + } + got := ev.Sandbox() + if tt.wantUID == "" && tt.wantName == "" { + if got != nil { + t.Fatalf("Sandbox() = %+v, want nil", got) + } + return + } + if got == nil || got.UID != tt.wantUID || got.Name != tt.wantName { + t.Fatalf("Sandbox() = %+v, want uid %q name %q", got, tt.wantUID, tt.wantName) + } + }) + } +} + func TestEventDestination(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/hydrator/fixtures_test.go b/internal/hydrator/fixtures_test.go index b584968..b52b8f2 100644 --- a/internal/hydrator/fixtures_test.go +++ b/internal/hydrator/fixtures_test.go @@ -88,6 +88,36 @@ func jsonLine(t *testing.T, fields map[string]any) string { return string(b) } +// moveSandboxToDevice rewrites the records on lines selected by move to carry +// their sandbox in device.container, the shape OpenShell emits after v0.1.7. +// Lines without a JSON object or without a container are left untouched. +func moveSandboxToDevice(t *testing.T, input string, move func(line string) bool) string { + t.Helper() + lines := strings.Split(input, "\n") + for i, line := range lines { + start := strings.IndexByte(line, '{') + if start < 0 || !move(line) { + continue + } + fields := exampleFields(t, line[start:]) + container, ok := fields["container"] + if !ok { + continue + } + delete(fields, "container") + device, _ := fields["device"].(map[string]any) + if device == nil { + device = map[string]any{} + } + device["container"] = container + fields["device"] = device + lines[i] = line[:start] + jsonLine(t, fields) + } + return strings.Join(lines, "\n") +} + +func everyLine(string) bool { return true } + func exampleFields(t *testing.T, example string) map[string]any { t.Helper() var fields map[string]any diff --git a/internal/hydrator/run.go b/internal/hydrator/run.go index 2c764eb..7ac3bed 100644 --- a/internal/hydrator/run.go +++ b/internal/hydrator/run.go @@ -65,7 +65,7 @@ type Report struct { // inside the window, whatever their class. Attributed int // Unmapped counts events that resolved to no agent; NoContainer is the - // subset that carried no container at all, as downgraded records do. + // subset that carried no sandbox at all (see Event.Sandbox). Unmapped int NoContainer int // OutOfWindow counts events excluded by an explicit Options.Window. diff --git a/internal/hydrator/run_test.go b/internal/hydrator/run_test.go index 313af44..7707061 100644 --- a/internal/hydrator/run_test.go +++ b/internal/hydrator/run_test.go @@ -7,6 +7,8 @@ import ( "errors" "io" "log/slog" + "os" + "path/filepath" "reflect" "strings" "testing" @@ -156,6 +158,109 @@ func TestRunEndToEnd(t *testing.T) { } } +func TestRunDeviceContainerShape(t *testing.T) { + t.Parallel() + run := func(t *testing.T, input string) (Report, []byte) { + t.Helper() + sink := &fakeSink{} + opts := Options{Agents: sampleAgents(t), Signals: DefaultSignalConfig(), AIMID: sampleAIMID, EvidenceURL: sampleEvidURL, Clock: fixedClock} + report, err := Run(t.Context(), strings.NewReader(input), sink, opts, nil) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + if len(sink.batches) != 1 { + t.Fatalf("sink received %d batches, want 1", len(sink.batches)) + } + return report, sink.batches[0].Body + } + original := sampleInput(t) + wantReport, wantBody := run(t, original) + tests := []struct { + name string + move func(line string) bool + wantMoved int + }{ + {name: "every sandbox upgraded", move: everyLine, wantMoved: 6}, + {name: "mixed fleet", move: func(line string) bool { return strings.Contains(line, sandboxAUID) }, wantMoved: 3}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + input := moveSandboxToDevice(t, original, tt.move) + if got := strings.Count(input, `"device":{"container"`); got != tt.wantMoved { + t.Fatalf("moved %d records, want %d", got, tt.wantMoved) + } + report, body := run(t, input) + if !reflect.DeepEqual(report, wantReport) { + t.Fatalf("Report = %+v\nwant %+v", report, wantReport) + } + if !bytes.Equal(body, wantBody) { + t.Fatalf("batch body differs:\n%s\nwant\n%s", body, wantBody) + } + }) + } +} + +// testdata/supervisor-device-container.jsonl holds unedited records from a +// supervisor built at NVIDIA/OpenShell main 1cdfe70, the first shape that +// carries the sandbox only in device.container. +func TestRunUpstreamDeviceContainerCapture(t *testing.T) { + t.Parallel() + const ( + captureSandboxUID = "5fa11a3e-6497-4f23-aaf2-72749cb9a9f6" + captureSandboxName = "ans-54692" + captureRecords = 14 + ) + capture, err := os.ReadFile(filepath.Join("testdata", "supervisor-device-container.jsonl")) + if err != nil { + t.Fatalf("read capture: %v", err) + } + for i, line := range strings.Split(strings.TrimSpace(string(capture)), "\n") { + var record struct { + Container json.RawMessage `json:"container"` + Device struct { + Container *Container `json:"container"` + } `json:"device"` + } + if err := json.Unmarshal([]byte(line), &record); err != nil { + t.Fatalf("capture line %d: %v", i+1, err) + } + if record.Container != nil || record.Device.Container == nil || record.Device.Container.UID != captureSandboxUID { + t.Fatalf("capture line %d is not the device.container shape: %s", i+1, line) + } + } + tests := []struct { + name string + mapping AgentMapping + }{ + {name: "mapped by uid", mapping: AgentMapping{AgentID: "agent-a", AnsName: "ans://v1.0.0.a.example.com", SandboxUIDs: []string{captureSandboxUID}}}, + {name: "mapped by name", mapping: AgentMapping{AgentID: "agent-a", AnsName: "ans://v1.0.0.a.example.com", SandboxNames: []string{captureSandboxName}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + signals := DefaultSignalConfig() + signals.EmitActivity = true + sink := &fakeSink{} + opts := Options{Agents: mustAgentMap(t, tt.mapping), Signals: signals, AIMID: sampleAIMID, EvidenceURL: sampleEvidURL, Clock: fixedClock} + report, err := Run(t.Context(), bytes.NewReader(capture), sink, opts, nil) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + if report.Parse.Events != captureRecords || report.Attributed != captureRecords || report.Unmapped != 0 || report.NoContainer != 0 { + t.Fatalf("Report = %+v, want all %d records attributed", report, captureRecords) + } + wantAgents := []AgentSummary{{AgentID: "agent-a", AnsName: "ans://v1.0.0.a.example.com", Events: captureRecords, Denied: 2, Observations: 3}} + if !reflect.DeepEqual(report.Agents, wantAgents) { + t.Fatalf("Agents = %+v, want %+v", report.Agents, wantAgents) + } + if !report.Posted || len(sink.batches) != 1 || len(sink.batches[0].Observations) != 3 { + t.Fatalf("Posted = %v, batches = %+v", report.Posted, sink.batches) + } + }) + } +} + func TestRunSignsBatch(t *testing.T) { t.Parallel() id := testfixtures.MustNewIdentity("agent-signing", "ans://v1.0.0.platform.example.com") diff --git a/internal/hydrator/testdata/supervisor-device-container.jsonl b/internal/hydrator/testdata/supervisor-device-container.jsonl new file mode 100644 index 0000000..3b21cfc --- /dev/null +++ b/internal/hydrator/testdata/supervisor-device-container.jsonl @@ -0,0 +1,14 @@ +{"class_uid":0,"class_name":"Base Event","category_uid":0,"category_name":"Uncategorized","activity_id":99,"activity_name":"Relay open","type_uid":99,"type_name":"Base Event: Relay open","time":1793900581821,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"ssh relay open (channel_id=e7c66071-c4c3-48b5-b480-47233ec18383, target=unix:/run/openshell/ssh.sock)","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["container","host"],"uid":"f40ddfc6-35a9-4dd7-91e2-693c3cf21229"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}}} +{"class_uid":4007,"class_name":"SSH Activity","category_uid":4,"category_name":"Network Activity","activity_id":1,"activity_name":"Open","type_uid":400701,"type_name":"SSH Activity: Open","time":1793900581821,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"SSH connection accepted on supervisor Unix socket","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","container","host"],"uid":"aecbc9f2-af7e-426b-ae4d-ce1908fcf000"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"dst_endpoint":{"name":"/run/openshell/ssh.sock","svc_name":"ssh"},"action_id":1,"action":"Allowed","disposition_id":1,"disposition":"Allowed"} +{"class_uid":5019,"class_name":"Device Config State Change","category_uid":5,"category_name":"Discovery","activity_id":1,"activity_name":"Log","type_uid":501901,"type_name":"Device Config State Change: Log","time":1793900581835,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"Policy DNS mapped host.openshell.internal resolved=172.20.10.12 synthetic=198.18.0.2 ports=37423 mapping_id=1e989877-91c7-48b0-8ea6-8cacc61d76c1","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","container","host"],"uid":"5673a01e-9428-43e2-aeab-e74b8c5dc8d3"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"unmapped":{"normalized_domain":"host.openshell.internal","address_family":"Ipv4","approved_real_ip_candidates":["172.20.10.12"],"synthetic_ip":"198.18.0.2","allowed_ports":[37423],"policy_generation":2,"mapping_generation":6,"mapping_id":"1e989877-91c7-48b0-8ea6-8cacc61d76c1"},"state_id":2,"state":"published"} +{"class_uid":4001,"class_name":"Network Activity","category_uid":4,"category_name":"Network Activity","activity_id":1,"activity_name":"Open","type_uid":400101,"type_name":"Network Activity: Open","time":1793900581838,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"CONNECT_L7 allowed host.openshell.internal:37423","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","network_proxy","container","host"],"uid":"d795c967-97df-4a0c-a7c8-4a04652c6ec2"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"src_endpoint":{"ip":"0.0.0.0","port":0},"dst_endpoint":{"domain":"host.openshell.internal","port":37423},"proxy_endpoint":{"ip":"127.0.0.1","port":3128},"actor":{"process":{"name":"/usr/bin/curl","pid":0,"cmd_line":"-","parent_process":{"name":"-","pid":0}}},"firewall_rule":{"name":"callee","type":"opa"},"action_id":1,"action":"Allowed","disposition_id":1,"disposition":"Allowed","is_src_dst_assignment_known":true} +{"class_uid":4002,"class_name":"HTTP Activity","category_uid":4,"category_name":"Network Activity","activity_id":99,"activity_name":"Other","type_uid":400299,"type_name":"HTTP Activity: Other","time":1793900581838,"severity_id":1,"severity":"Informational","message":"L7_REQUEST allow POST host.openshell.internal:37423/v1/tasks reason=","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","network_proxy","container","host"],"uid":"3f764c28-a9f9-4f95-9206-ffd073271241"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"http_request":{"http_method":"POST","url":{"scheme":"http","hostname":"host.openshell.internal","path":"/v1/tasks","port":37423}},"dst_endpoint":{"domain":"host.openshell.internal","port":37423},"proxy_endpoint":{"ip":"127.0.0.1","port":3128},"firewall_rule":{"name":"callee","type":"l7"},"action_id":1,"action":"Allowed","disposition_id":1,"disposition":"Allowed","observation_point_id":2} +{"class_uid":4002,"class_name":"HTTP Activity","category_uid":4,"category_name":"Network Activity","activity_id":99,"activity_name":"Other","type_uid":400299,"type_name":"HTTP Activity: Other","time":1793900581839,"severity_id":1,"severity":"Informational","message":"MIDDLEWARE ans-identity ans-identity-smoke decision=Allow","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","network_proxy","container","host"],"uid":"7bce6ea0-eaf3-403d-bdf9-564ad69abdfa"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"unmapped":{"transformed":true,"failed":false},"http_request":{"http_method":"POST","url":{"scheme":"http","hostname":"host.openshell.internal","path":"/v1/tasks","port":37423}},"dst_endpoint":{"domain":"host.openshell.internal","port":37423},"proxy_endpoint":{"ip":"127.0.0.1","port":3128},"firewall_rule":{"name":"callee","type":"middleware"},"action_id":1,"action":"Allowed","disposition_id":1,"disposition":"Allowed","observation_point_id":2} +{"class_uid":2004,"class_name":"Detection Finding","category_uid":2,"category_name":"Findings","activity_id":1,"activity_name":"Create","type_uid":200401,"type_name":"Detection Finding: Create","time":1793900581839,"severity_id":3,"severity":"Medium","message":"Middleware finding ans-identity-smoke.finding count=1","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","container","host"],"uid":"b216e035-af5b-4730-8788-a7d13ef835b3"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"unmapped":{"middleware":"ans-identity","count":1},"finding_info":{"uid":"ans-identity-smoke.finding","title":"External middleware finding"},"evidences":[{"data":{"middleware":"ans-identity","count":"1"}}]} +{"class_uid":0,"class_name":"Base Event","category_uid":0,"category_name":"Uncategorized","activity_id":99,"activity_name":"Relay closed","type_uid":99,"type_name":"Base Event: Relay closed","time":1793900581842,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"ssh relay closed (channel_id=e7c66071-c4c3-48b5-b480-47233ec18383, target=unix:/run/openshell/ssh.sock)","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["container","host"],"uid":"44423328-36c8-4327-b012-8bbf9973fd82"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}}} +{"class_uid":0,"class_name":"Base Event","category_uid":0,"category_name":"Uncategorized","activity_id":99,"activity_name":"Relay open","type_uid":99,"type_name":"Base Event: Relay open","time":1793900581886,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"ssh relay open (channel_id=c21d29d0-9508-438e-8c32-973fbb4723e6, target=unix:/run/openshell/ssh.sock)","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["container","host"],"uid":"ec24f326-ec9c-40f8-b68b-f6be5fcf172a"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}}} +{"class_uid":4007,"class_name":"SSH Activity","category_uid":4,"category_name":"Network Activity","activity_id":1,"activity_name":"Open","type_uid":400701,"type_name":"SSH Activity: Open","time":1793900581886,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"SSH connection accepted on supervisor Unix socket","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","container","host"],"uid":"26074dcf-5f63-4c8e-a84d-0229a0742bcb"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"dst_endpoint":{"name":"/run/openshell/ssh.sock","svc_name":"ssh"},"action_id":1,"action":"Allowed","disposition_id":1,"disposition":"Allowed"} +{"class_uid":4001,"class_name":"Network Activity","category_uid":4,"category_name":"Network Activity","activity_id":5,"activity_name":"Refuse","type_uid":400105,"type_name":"Network Activity: Refuse","time":1793900581899,"severity_id":3,"severity":"Medium","status_id":2,"status":"Failure","message":"Policy DNS refused a name that is not eligible in the active policy","status_detail":"policy_dns_ineligible","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","network_proxy","container","host"],"uid":"72fb1221-eee0-439b-83ae-48b0ef19b650"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"dst_endpoint":{"domain":"example.com"},"proxy_endpoint":{"ip":"127.0.0.1","port":3128},"action_id":2,"action":"Denied","disposition_id":2,"disposition":"Blocked","is_src_dst_assignment_known":true} +{"class_uid":5019,"class_name":"Device Config State Change","category_uid":5,"category_name":"Discovery","activity_id":1,"activity_name":"Log","type_uid":501901,"type_name":"Device Config State Change: Log","time":1793900581899,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"Policy DNS staged unapproved name example.com synthetic=198.18.0.3 for TCP policy review","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","container","host"],"uid":"ed67899d-cec2-4271-8820-85077a2da2d6"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"unmapped":{"normalized_domain":"example.com","address_family":"Ipv4","synthetic_ip":"198.18.0.3","policy_generation":2,"mapping_generation":7,"mapping_id":"234c7999-14ab-4486-ab0f-279344ecbfbc"},"state_id":2,"state":"observation"} +{"class_uid":4001,"class_name":"Network Activity","category_uid":4,"category_name":"Network Activity","activity_id":1,"activity_name":"Open","type_uid":400101,"type_name":"Network Activity: Open","time":1793900581900,"severity_id":3,"severity":"Medium","status_id":2,"status":"Failure","message":"Transparent TCP denied before relay: endpoint example.com:443 is not allowed by any policy","status_detail":"transparent_tcp_policy_denied","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["security_control","network_proxy","container","host"],"uid":"496cf106-c6a0-4c18-b24f-d81318f6dc6d"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}},"dst_endpoint":{"domain":"example.com","ip":"198.18.0.3","port":443},"proxy_endpoint":{"ip":"127.0.0.1","port":3128},"actor":{"process":{"name":"/usr/bin/curl","pid":0,"cmd_line":"/dev/null"}},"action_id":2,"action":"Denied","disposition_id":2,"disposition":"Blocked","is_src_dst_assignment_known":true} +{"class_uid":0,"class_name":"Base Event","category_uid":0,"category_name":"Uncategorized","activity_id":99,"activity_name":"Relay closed","type_uid":99,"type_name":"Base Event: Relay closed","time":1793900581902,"severity_id":1,"severity":"Informational","status_id":1,"status":"Success","message":"ssh relay closed (channel_id=c21d29d0-9508-438e-8c32-973fbb4723e6, target=unix:/run/openshell/ssh.sock)","metadata":{"version":"1.8.0","product":{"name":"OpenShell Sandbox Supervisor","vendor_name":"OpenShell","version":"0.1.8-dev.29+g1cdfe7088"},"profiles":["container","host"],"uid":"a942c8be-495c-4d5f-ad36-d235e194cf9a"},"device":{"hostname":"docker-desktop","type_id":99,"type":"Sandbox","container":{"name":"ans-54692","uid":"5fa11a3e-6497-4f23-aaf2-72749cb9a9f6","image":{"uid":"sha256:d91247ff6a138bba48803d62f78f45897072d007c6c2c4d7fee2e89c975ef1b6","name":"ans-openshell-smoke-workload:1419058764"}},"os":{"name":"Linux","type_id":200,"type":"Linux"}}}