Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
348152c
sys.firmware: Add microversion to bmc.version
wdoekes Jan 27, 2026
8291ffe
rmq2nb: Fix validation of network ID and broadcast addresses
Urth Jan 28, 2026
34ff979
app.ps-kvmex1: Do not die on new JSON kvm args
wdoekes Mar 16, 2026
5e2e843
sys.firmware: Fix fluctuating Created args
wdoekes Mar 16, 2026
d6f49e4
build: Move to go modules (GO111MODULE=on)
wdoekes Apr 9, 2026
c1e61cc
tests: Fix TestParseArgsOrExit_NoOptions for newer golang
wdoekes Apr 9, 2026
39f50c8
deps: Bump requests dependency
wdoekes Apr 9, 2026
0dae9b4
cleanup: Run make pretty
wdoekes Apr 9, 2026
32f62fb
cleanup: More make pretty
wdoekes Apr 9, 2026
5a9c657
os.keys: Fix so sshd_config Includes are read for ssh key location
wdoekes Apr 13, 2026
da2d274
sys.storage: Correctly show logical sector size for non-nvme
wdoekes Apr 14, 2026
aad51b6
os.uptime: Fix fluctuating uptime
wdoekes Apr 22, 2026
ec1c39b
core: Add stable-collector spool/mode feature for app.* collectors
wdoekes Apr 27, 2026
5b24dcd
app.psdiff: Add psdiff.dump next to psdiff.db
wdoekes Apr 27, 2026
0afdb79
core: Replace deprecated ioutil.ReadDir
wdoekes Apr 27, 2026
0ca5d6d
cleanup: Run make pretty
wdoekes Apr 27, 2026
d53ab1c
core: gocollect -k (--test-keys) now implies -s (--one-shot)
wdoekes Apr 27, 2026
220a3c9
version: Bump to 0.9.9
wdoekes Apr 27, 2026
7fa5060
app.needsrestart: New collector to find unrestarted libs/bins
wdoekes Apr 27, 2026
29ad9f3
app.ossochange: New collector to fetch change tickets
wdoekes Apr 27, 2026
f50dce2
version: Bump to 0.9.10
wdoekes Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Changes
-------

* v0.9.10 [2026-04-27]:

- app.needsrestart: New collector to find unrestarted libs/bins
- app.ossochange: New collector to fetch change tickets

* v0.9.9 [2026-04-27]:

- core: Add stable-collector spool/mode feature for app.* collectors
- core: gocollect -k (--test-keys) now implies -s (--one-shot)
- app.psdiff: Add psdiff.dump next to psdiff.db
- app.ps-kvmex1: Do not die on new JSON kvm args
- os.keys: Fix so sshd_config Includes are read for ssh key location
- os.uptime: Fix fluctuating uptime
- sys.firmware: Add microversion to bmc.version
- sys.firmware: Fix fluctuating Created args
- sys.storage: Correctly show logical sector size for non-nvme
- rmq2nb: Fix validation of network ID and broadcast addresses

* v0.9.8 [2026-01-05]:

- sys.firmware: Add BMC board into to sys.firmware.
Expand Down
9 changes: 0 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,6 @@ And check this out inside that::
git clone https://github.com/ossobv/gocollect \
$GOPATH/src/github.com/ossobv/gocollect

And install prerequisites::

go get github.com/ossobv/go-getopt

Possibly set env to old style module handling::

# go.mod file not found in current directory or any parent directory...
go env -w GO111MODULE=off # sets ~/.config/go/env: GO111MODULE=off


Packaging for Debian
--------------------
Expand Down
12 changes: 5 additions & 7 deletions gocollect-client/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
prefix = /usr

SOURCES = $(shell find . -name '*.go' -type f | sort)
GODIRS = $(shell find . -name '*.go' -type f | sed -e 's:/[^/]*.go$$::' | sort -u)
SHCOLLECTORS = $(shell find collectors/ -maxdepth 1 -name '[a-z]*.*' -type f -perm /700 '!' -name '*.*.*' | sort)
# Debian version spec says:
# - tilde sorts before anything, so ~rc1 sorts before final
Expand All @@ -23,7 +22,7 @@ clean:
$(RM) gocollect

gocollect: $(SOURCES)
go build $(GOFLAGS) $(GOLDFLAGS) gocollect.go
go build -o gocollect $(GOFLAGS) $(GOLDFLAGS) .
if ldd gocollect | grep '=>'; then echo "ERROR: static linkage failed" >&2; \
$(RM) gocollect; false; fi

Expand Down Expand Up @@ -140,16 +139,15 @@ gocollect-$(TGZ_VERSION).tar.gz: gocollect-bin
.PHONY: check pretty testrun
testrun: gocollect-bin
#GOTRACEBACK=system strace -tt -fbexecve ./gocollect -c gocollect-test.conf
sudo env GOPATH=$$GOPATH GOTRACEBACK=system ./gocollect -c gocollect-test.conf
sudo env GOTRACEBACK=system ./gocollect -c gocollect-test.conf

check: pretty
for d in $(GODIRS); do (cd $$d && go test); done
go test ./...

pretty:
git ls-files | grep '\.go$$' | while read x; do gofmt -d "$$x" | patch $$x; done
for d in $(GODIRS); do golint $$d && (cd $$d && go vet); done
golint ./... && go vet ./...

# .PHONY: fetch-new-package
# fetch-new-package:
# # Be sure to set GOPATH; see ./gorc.
# go get github.com/XXX
# go get github.com/XXX@latest
21 changes: 21 additions & 0 deletions gocollect-client/collectors/app.fwdiff
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
# vim: set ts=8 sw=4 sts=4 et ai:
# REQUIRES: awk(awk)
# NOTE: Remember to test changes with mawk(1).

lines2js() {
awk '
{gsub("\"","");gsub("\\\\","");if(NR>1)printf ",";print "\"" $0 "\""}'
}

if command -v fwdiff >/dev/null; then
echo '{"fwdiff.dump":{"filelines":['
fwdiff dump 2>/dev/null | lines2js
if test -f /var/lib/fwdiff.db; then
echo ']},"fwdiff.db":{"filelines":['
lines2js < /var/lib/fwdiff.db
fi
echo ']}}'
else
echo '{}'
fi
194 changes: 194 additions & 0 deletions gocollect-client/collectors/app.needsrestart
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""
gocollect collector: app.needsrestart

Reports packages that need a restart (i.e. they are not up to date),
systemd services that need a restart (i.e. they are either not up to date, or
they are using old libraries; see previous). Also reports other executables
which cannot be pinpointed to a systemd service.

This is used as part of the custom authenticated scanning implementation.

Output might look like:

{
// Packages that we cannot "trust" to be up to date yet.
"apt_packages": [
"gocollect",
"libcap2",
"libpam-cap"
],
// Local services that will need some kind of restart.
"systemd_services": [
"dnsmasq.service",
"docker.service",
"fwupd.service",
"gocollect.service",
"lldpd.service"
],
// Not found by 'systemd status PID'. Could be in LXC or other namespace
"other_executables": [
"/usr/bin/dockerd",
"/usr/bin/sudo",
"/usr/lib/postgresql/14/bin/postgres"
]
}
"""
import sys
from json import dumps as json_dumps
from os import geteuid, listdir, readlink, stat
from subprocess import SubprocessError, check_output


class Process:
__slots__ = ('pid', 'exe', 'deleted', '_systemd_service')
FILE_PACKAGES = {} # (devid, file) => some-apt-package

@classmethod
def get_all(cls):
for entry in listdir('/proc'):
if entry.isdigit():
yield cls.from_pid(entry)

@classmethod
def from_pid(cls, pid):
return cls(pid)

def __init__(self, pid):
self.pid = pid
try:
self.exe = readlink(f'/proc/{pid}/exe')
except OSError:
self.exe = None
return

self.deleted = set()
if self.exe.endswith(' (deleted)'):
self.exe = self.exe[0:-10]
self.deleted.add(self.exe)

# Check proc maps. All of them.
try:
with open(f'/proc/{pid}/maps') as fp:
for line in fp:
line = line.rstrip()
if not line.endswith(' (deleted)'):
continue
# format: addr perms offset dev inode pathname
parts = line.split(None, 5)
if len(parts) >= 6:
path = parts[5]
assert path.endswith(' (deleted)'), path
path = path[0:-10]
if (path.startswith('/')
and (path.endswith('.so') or '.so.' in path)):
self.deleted.add(path)
except OSError:
self.exe = None

self._systemd_service = None

@staticmethod
def _usrmerge_options(path):
# After usrmerge some package have files in /lib while they are
# accessed as /usr/lib.
if path.startswith('/usr/'):
return (path, path[4:])
return (path,)

@property
def packages_needing_restart(self):
"""Return apt packages for deleted executable files."""
packages = set()

# Rightfully assume that places where /var/lib/dpkg has the same
# filesystem, the library we're looking for has the same filesystem as
# well.
dpkg_path = f'/proc/{self.pid}/root/var/lib/dpkg'
try:
st = stat(dpkg_path)
except OSError:
return packages
devid = st.st_dev

for deleted in self.deleted:
for exe in self._usrmerge_options(deleted):
if (devid, exe) in self.FILE_PACKAGES:
packages.add(self.FILE_PACKAGES[(devid, exe)])
break

try:
with open('/dev/null', 'w') as DEVNULL:
result = check_output(
('dpkg', f'--admindir={dpkg_path}', '-S', exe),
stderr=DEVNULL, timeout=15, env={'LC_ALL': 'C'})
except (SubprocessError, FileNotFoundError):
pass
else:
# Ex: systemd-timesyncd: /usr/lib/systemd/systemd-timesyncd
lines = result.decode('utf-8', 'replace').split('\n')
lines = [
line for line in lines if line.endswith(f': {exe}')]
if lines:
pkg = sorted(lines)[0].split(':', 1)[0]
packages.add(pkg)
self.FILE_PACKAGES[(devid, exe)] = pkg
break

return list(packages)

@property
def systemd_service(self):
"""Return systemd unit name for pid, or None."""
if self._systemd_service is None:
try:
with open('/dev/null', 'w') as DEVNULL:
result = check_output(
('systemctl', 'status', str(self.pid)),
stderr=DEVNULL, timeout=15, env={'LC_ALL': 'C'})
except (SubprocessError, FileNotFoundError):
return None

service = (
result.decode('utf-8', 'replace')
.split('\n', 1)[0][2:].split(' - ', 1)[0])
if service.endswith('.service'):
self._systemd_service = service
else:
self._systemd_service = False
return self._systemd_service


def collect():
packages_needing_restart = set()
services_needing_restart = set()
unknown_needing_restart = set()

for process in Process.get_all():
if not process.exe or not process.deleted:
continue

packages_needing_restart.update(process.packages_needing_restart)

if process.systemd_service:
services_needing_restart.add(process.systemd_service)
else:
unknown_needing_restart.add(process.exe)

return {
'apt_packages': sorted(packages_needing_restart),
'systemd_services': sorted(services_needing_restart),
'other_executables': sorted(unknown_needing_restart),
}


def main():
if geteuid() != 0:
print('warning: running without root; /proc entries may be unreadable',
file=sys.stderr)

print(json_dumps(collect()))


if __name__ == '__main__':
main()
14 changes: 14 additions & 0 deletions gocollect-client/collectors/app.ossochange
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
# vim: set ts=8 sw=4 sts=4 et ai:
# LABELS: optional
# REQUIRES: jq(jq)
# REQUIRES: sed(sed)
# REQUIRES: systemd(journalctl)

if command -v journalctl >/dev/null && command -v jq >/dev/null; then
journalctl -t osso-change -S '-1 day' -o cat | jq -s "\
map(select(.action==\"workon\" and .ticket) | .ticket) \
| unique | {tickets: .}" 2>/dev/null
else
echo '{}'
fi
28 changes: 15 additions & 13 deletions gocollect-client/collectors/app.ps-kvmex1
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,18 @@ valtodict() {

echo "["
n=0
ps h -o cmd -p $(pidof kvm qemu-kvm qemu-system-x86_64 | tr ' ' ,) \
2>/dev/null | while read kvmcmd; do
all='*'
for pid in $(pidof kvm qemu-kvm qemu-system-x86_64); do
match_first='*'
optkey=
test $n -gt 0 && echo -n ,
n=$((n+1))
used_keys=()
n2=0
for cmd in $kvmcmd; do
while IFS= read -r -d '' cmd; do
n2=$((n2+1))
case $cmd in
$all)
echo -n "{\"argv0\":\"$cmd\""
$match_first)
echo -n ",{\"argv0\":\"$cmd\""
bin=$cmd
all=
match_first=
;;
-*)
test -n "$optkey" && echo -n ",\"$optkey\":true"
Expand All @@ -50,12 +47,17 @@ ps h -o cmd -p $(pidof kvm qemu-kvm qemu-system-x86_64 | tr ' ' ,) \
used_keys+=($optkey)
;;
*)
echo -n ",\"$optkey\":$(valtodict "$cmd")"
if [[ "${cmd:0:1}" == "{" ]]; then
echo -n ",\"$optkey\":$cmd"
else
echo -n ",\"$optkey\":$(valtodict "$cmd")"
fi
optkey=
;;
esac
done
done < /proc/$pid/cmdline || continue 2>/dev/null
n=$((n+1))
test -n "$optkey" && echo -n ",\"$optkey\":true"
echo ",\"argv_all\":\"$kvmcmd\"}"
done
echo '}'
done 2>/dev/null | LC_ALL=C sort | sed -e '1s/^,//'
echo "]"
21 changes: 16 additions & 5 deletions gocollect-client/collectors/app.psdiff
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@
# REQUIRES: awk(awk)
# NOTE: Remember to test changes with mawk(1).

test -f /var/lib/psdiff.db && exec awk '
BEGIN{print "{\"psdiff.db\":{\"filelines\":["}
{gsub("\\\"","");gsub("\\\\","");if(NR>1)printf ",";print "\"" $0 "\"" }
END{print "]}}"}' < /var/lib/psdiff.db
echo '{}'
lines2js() {
awk '
{gsub("\"","");gsub("\\\\","");if(NR>1)printf ",";print "\"" $0 "\""}'
}

if command -v psdiff >/dev/null; then
echo '{"psdiff.dump":{"filelines":['
psdiff --net dump | lines2js
if test -f /var/lib/psdiff.db; then
echo ']},"psdiff.db":{"filelines":['
lines2js < /var/lib/psdiff.db
fi
echo ']}}'
else
echo '{}'
fi
Loading
Loading