feat(go): advance validation sidecar toward production
This commit is contained in:
parent
6a06eba255
commit
f85c2e831a
92 changed files with 8881 additions and 91 deletions
139
go-agent/internal/events/host_event.go
Normal file
139
go-agent/internal/events/host_event.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HostEvent is the common typed record for network, filesystem, identity,
|
||||
// capability, and module activity that is neither exec nor raw syscall data.
|
||||
type HostEvent struct {
|
||||
Event string `json:"event"`
|
||||
PID int `json:"pid"`
|
||||
PPID int `json:"ppid"`
|
||||
UID int `json:"uid"`
|
||||
Comm string `json:"comm"`
|
||||
ParentComm string `json:"parent_comm"`
|
||||
Path string `json:"path"`
|
||||
Argv []string `json:"argv"`
|
||||
PeerIP string `json:"peer_ip"`
|
||||
PeerPort int `json:"peer_port"`
|
||||
LocalIP string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
TargetUID int `json:"target_uid"`
|
||||
TargetGID int `json:"target_gid"`
|
||||
Mode int `json:"mode"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
ModuleName string `json:"module_name"`
|
||||
}
|
||||
|
||||
func (e HostEvent) ArgvString() string {
|
||||
parts := make([]string, 0, len(e.Argv)+1)
|
||||
parts = append(parts, e.Path)
|
||||
parts = append(parts, e.Argv...)
|
||||
return strings.TrimSpace(strings.Join(parts, " "))
|
||||
}
|
||||
|
||||
// UnmarshalJSON accepts the same compatibility aliases as Python ruleops:
|
||||
// type/event, remote/bind endpoint names, numeric strings, singular capability,
|
||||
// and module name. Defaults of -1 for absent target IDs are semantically
|
||||
// significant because zero means a requested root transition.
|
||||
func (e *HostEvent) UnmarshalJSON(raw []byte) error {
|
||||
var values map[string]any
|
||||
if err := json.Unmarshal(raw, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
e.TargetUID, e.TargetGID = -1, -1
|
||||
e.Event = stringValue(firstValue(values, "event", "type"))
|
||||
e.PID = integerValue(values["pid"], 0)
|
||||
e.PPID = integerValue(values["ppid"], 0)
|
||||
e.UID = integerValue(values["uid"], 0)
|
||||
e.Comm = stringValue(values["comm"])
|
||||
e.ParentComm = stringValue(values["parent_comm"])
|
||||
e.Path = stringValue(values["path"])
|
||||
e.Argv = stringSlice(values["argv"])
|
||||
e.PeerIP = stringValue(firstValue(values, "peer_ip", "remote_ip"))
|
||||
e.PeerPort = integerValue(firstValue(values, "peer_port", "remote_port"), 0)
|
||||
e.LocalIP = stringValue(firstValue(values, "local_ip", "bind_ip", "listen_ip"))
|
||||
e.LocalPort = integerValue(firstValue(values, "local_port", "bind_port", "listen_port"), 0)
|
||||
e.TargetUID = integerValue(firstValue(values, "target_uid", "uid_target"), -1)
|
||||
e.TargetGID = integerValue(firstValue(values, "target_gid", "gid_target"), -1)
|
||||
e.Mode = integerValue(values["mode"], 0)
|
||||
e.Capabilities = stringSlice(firstValue(values, "capabilities", "caps", "cap_effective"))
|
||||
if len(e.Capabilities) == 0 {
|
||||
if capability := stringValue(values["capability"]); capability != "" {
|
||||
e.Capabilities = []string{capability}
|
||||
}
|
||||
}
|
||||
for index := range e.Capabilities {
|
||||
e.Capabilities[index] = strings.ToUpper(e.Capabilities[index])
|
||||
}
|
||||
e.ModuleName = stringValue(firstValue(values, "module_name", "name"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func firstValue(values map[string]any, keys ...string) any {
|
||||
for _, key := range keys {
|
||||
if value, ok := values[key]; ok && value != nil && stringValue(value) != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringValue(value any) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
|
||||
func integerValue(value any, fallback int) int {
|
||||
if value == nil {
|
||||
return fallback
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return int(typed)
|
||||
case int:
|
||||
return typed
|
||||
case string:
|
||||
text := strings.TrimSpace(strings.ToLower(typed))
|
||||
if strings.HasPrefix(text, "0o") {
|
||||
parsed, err := strconv.ParseInt(text[2:], 8, 64)
|
||||
if err == nil {
|
||||
return int(parsed)
|
||||
}
|
||||
}
|
||||
parsed, err := strconv.ParseInt(text, 0, 64)
|
||||
if err == nil {
|
||||
return int(parsed)
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func stringSlice(value any) []string {
|
||||
if text, ok := value.(string); ok {
|
||||
if text == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{text}
|
||||
}
|
||||
items, ok := value.([]any)
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
result = append(result, stringValue(item))
|
||||
}
|
||||
return result
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue