A live restart of the system bus during an upgrade hung every login on a workstation until a reboot. The module owns the bus's packages, declares the bus running with no restart or reload trigger, publishes only curated events (health, services, denials; never traffic) and serves tools to look at both buses.
115 lines
3.9 KiB
Go
115 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// BusUnits are the system bus's units as the journal knows them: dbus-broker's own name, and the
|
|
// alias every implementation answers to.
|
|
var BusUnits = []string{"dbus-broker.service", "dbus.service"}
|
|
|
|
// JournalLines is the most journal entries one read takes.
|
|
const JournalLines = 2000
|
|
|
|
// Denial is one policy denial as the bus logged it: the header of the message it refused, never its
|
|
// body, which the bus does not log either.
|
|
type Denial struct {
|
|
At string `json:"at"`
|
|
Action string `json:"action,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Sender string `json:"sender,omitempty"`
|
|
Destination string `json:"destination,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Interface string `json:"interface,omitempty"`
|
|
Member string `json:"member,omitempty"`
|
|
Policy string `json:"policy,omitempty"`
|
|
}
|
|
|
|
// key is what makes two denials the same example: who was refused what, ignoring the sender's unique
|
|
// name, which differs at every connection.
|
|
func (d Denial) key() string {
|
|
return d.Action + "|" + d.Type + "|" + d.Destination + "|" + d.Interface + "|" + d.Member
|
|
}
|
|
|
|
// journalEntry is the part of a journal entry the module reads. MESSAGE is read only to recognise a
|
|
// denial; it is never answered or published.
|
|
type journalEntry struct {
|
|
Cursor string `json:"__CURSOR"`
|
|
Realtime string `json:"__REALTIME_TIMESTAMP"`
|
|
Message any `json:"MESSAGE"`
|
|
Action string `json:"DBUS_BROKER_TRANSMIT_ACTION"`
|
|
Type string `json:"DBUS_BROKER_MESSAGE_TYPE"`
|
|
Sender string `json:"DBUS_BROKER_SENDER_UNIQUE_NAME"`
|
|
Destination string `json:"DBUS_BROKER_MESSAGE_DESTINATION"`
|
|
Path string `json:"DBUS_BROKER_MESSAGE_PATH"`
|
|
Interface string `json:"DBUS_BROKER_MESSAGE_INTERFACE"`
|
|
Member string `json:"DBUS_BROKER_MESSAGE_MEMBER"`
|
|
Policy string `json:"DBUS_BROKER_POLICY_TYPE"`
|
|
}
|
|
|
|
// IsDenial is whether a bus's log line is a policy denial: dbus-broker's "A security policy denied",
|
|
// or the reference daemon's "Rejected send message".
|
|
func IsDenial(message string) bool {
|
|
return strings.Contains(message, "security policy denied") || strings.Contains(message, "Rejected send message") ||
|
|
strings.Contains(message, "Rejected receive message")
|
|
}
|
|
|
|
// ParseDenials reads journalctl's JSON lines and answers the denials among them and the last cursor.
|
|
func ParseDenials(out string) ([]Denial, string) {
|
|
var got []Denial
|
|
cursor := ""
|
|
for _, line := range strings.Split(out, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || line[0] != '{' {
|
|
continue
|
|
}
|
|
var e journalEntry
|
|
if json.Unmarshal([]byte(line), &e) != nil {
|
|
continue
|
|
}
|
|
if e.Cursor != "" {
|
|
cursor = e.Cursor
|
|
}
|
|
msg, _ := e.Message.(string) // a binary MESSAGE comes as an array of bytes and is no denial
|
|
if !IsDenial(msg) {
|
|
continue
|
|
}
|
|
at := ""
|
|
if us, err := strconv.ParseInt(e.Realtime, 10, 64); err == nil {
|
|
at = time.UnixMicro(us).UTC().Format(time.RFC3339)
|
|
}
|
|
got = append(got, Denial{At: at, Action: e.Action, Type: e.Type, Sender: e.Sender,
|
|
Destination: e.Destination, Path: e.Path, Interface: e.Interface, Member: e.Member, Policy: e.Policy})
|
|
}
|
|
return got, cursor
|
|
}
|
|
|
|
func journalArgs() []string {
|
|
args := []string{"--no-pager", "-o", "json", "-n", strconv.Itoa(JournalLines)}
|
|
for _, u := range BusUnits {
|
|
args = append(args, "-u", u)
|
|
}
|
|
return args
|
|
}
|
|
|
|
// Denials is the watcher's Journal on this machine: journalctl as the operator's account, which
|
|
// reads the system journal through its group.
|
|
func (m *Machine) Denials(ctx context.Context, after string, since time.Time) ([]Denial, string, error) {
|
|
args := journalArgs()
|
|
if after != "" {
|
|
args = append(args, "--after-cursor", after)
|
|
} else {
|
|
args = append(args, "--since", "@"+strconv.FormatInt(since.Unix(), 10))
|
|
}
|
|
out, err := m.Run(ctx, "journalctl", args...)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
d, cursor := ParseDenials(out)
|
|
return d, cursor, nil
|
|
}
|