dbus: hold node-message-bus, and never restart the bus live (hq ADR 0215)
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.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user