FYI, attached are my monit systemd units.
They are definitely "too hardened" for some users.
You can PROBABLY just take everything before the hardening part, and use that 
as-is.

In particular, I deliberately prevent monit running as root
(I want systemd to restart units; I just want monit to check the availability 
of remote hosts).
This required some kludges because (IIRC) monit wants to have write access to 
its own config (like cupsd, or a crappy PHP webapp).

The NRPE replacement's "server" side should be here:
https://github.com/cyberitsolutions/die-nrpe-die
but I never got around to git filter-repo'ing the publishable parts out, so
I've attached a minified version of that as well.
[Unit]
Description=Pro-active monitoring utility for unix systems
After=network-online.target
Documentation=man:monit(1) https://mmonit.com/wiki/Monit/HowTo


# NOTE: "monit --interactive" means Run_Foreground and skips daemonize() 
(Type=simple).
#       This prevents systemd distinguishing "starting" from "started" 
(Type=forking).  Meh?
#       This DOES NOT prevent monit doing a weird "if monit is already running, 
just kill -USR1 it, and exit(0)"!
#           
https://bitbucket.org/tildeslash/monit/src/release-5-32-0/src/monit.c#lines-195:205
#           
https://bitbucket.org/tildeslash/monit/src/release-5-32-0/src/monit.c#lines-563:584
#       Overall, I think Type=forking gives systemd slightly better information.
#       Type=forking is required if you want to e.g. Before=nginx.service.
#
# NOTE: "monit reload" is basically "kill -HUP $MAINPID", but
#       it doesn't require "apt install procps".
[Service]
Type=forking
ExecStartPre=monit --test
ExecStart=monit
ExecReload=monit reload


# FIXME: sometimes on "systemctl start monit" or "systemctl restart monit",
#        monit simply exits immediately.
#        This can happen after unattended-upgrades + needrestart restart monit
#        due to a library getting a security patch!
#
#
# /join irc://irc.cyber.com.au/#cyber
# 11:11 <twb> Why is monit down
# 11:12 <twb> Something stopped it at 6:25 which is when cron.daily runs
# 11:16 <twb> 2022-11-20T06:25:01.984164+11:00 heavy systemd[1]: 
cron-daily.service: Succeeded.
# 11:16 <twb> 2022-11-20T06:25:05.905322+11:00 heavy systemd[1]: Stopping 
Apt-Cacher NG software download proxy...
# 11:16 <twb> 2022-11-20T06:25:05.906880+11:00 heavy systemd[1]: Stopping 
Statistics collection and monitoring daemon...
# 11:16 <twb> 2022-11-20T06:25:05.910253+11:00 heavy systemd[1]: Stopping 
Pro-active monitoring utility for unix systems...
# 11:16 <twb> ...why?
# 11:17 <twb> Maybe needrestart did it in response to a package upgrade
# 11:17 <twb> 2022-11-20T06:25:08.540726+11:00 heavy systemd[1]: Starting 
Statistics collection and monitoring daemon...
# 11:17 <twb> ...but then
# 11:17 <twb> it doesn't actually start properly
# 11:17 <twb> So I think there's two things here:
# 11:18 <twb> 1) something in morning cron restarted a bunch of services
# 11:18 <twb> 2) sometimes monit doesn't restart properly, and instead simply 
exits
# 11:18 <twb> I had seen (2) before not after I stopped messing with it
# 11:18 <twb> Same thing again when I run it by hand just now
# 11:20 <twb> monit is doing something like exit(0) for no reason
# 11:20 <twb> but not deterministically
# 11:20 <mike> An ugly workaround might be to set 'restart=always' instead of 
just on-error
# 12:06 <twb> mike: I've gotten nowhere debugging this
# 12:07 <twb> mike: I'm going to do what you suggest
#
# /join ircs://irc.libera.chat/#monit
# 11:21 <twb> So I have this problem where SOMETIMES monit when told to start 
as a daemon, will instead just exit immediately
# 11:21 <twb> http://ix.io/4gHg
#
#               twb@heavy:~$ sudo systemctl start monit
#               [sudo] password for twb:
#               twb@heavy:~$ sudo systemctl status monit
#               ● monit.service - Pro-active monitoring utility for unix systems
#                    Loaded: loaded (/etc/systemd/system/monit.service; 
enabled; vendor preset: enabled)
#                    Active: inactive (dead) since Thu 2022-11-24 11:18:40 
AEDT; 3s ago
#                      Docs: man:monit(1)
#                            https://mmonit.com/wiki/Monit/HowTo
#                   Process: 2059508 ExecStartPre=chown -h monit: 
/etc/monit/monitrc (code=exited, status=0/SUCCESS)
#                   Process: 2059509 ExecStartPre=chmod 0600 /etc/monit/monitrc 
(code=exited, status=0/SUCCESS)
#                   Process: 2059510 ExecStart=monit (code=exited, 
status=0/SUCCESS)
#                  Main PID: 2059512 (code=exited, status=0/SUCCESS)
#                       CPU: 272ms
#
#               Nov 24 11:18:40 heavy systemd[1]: Starting Pro-active 
monitoring utility for unix systems...
#               Nov 24 11:18:40 heavy monit[2059510]: Starting Monit 5.27.2 
daemon with http interface at [*]:2812
#               Nov 24 11:18:40 heavy monit[2059510]: Starting Monit 5.27.2 
daemon with http interface at [*]:2812
#               Nov 24 11:18:40 heavy systemd[1]: Started Pro-active monitoring 
utility for unix systems.
#               Nov 24 11:18:40 heavy monit[2059513]: 'heavy' Monit 5.27.2 
started
#               Nov 24 11:18:40 heavy monit[2059513]: Monit daemon with pid 
[2059513] stopped
#               Nov 24 11:18:40 heavy monit[2059513]: 'heavy' Monit 5.27.2 
stopped
#               Nov 24 11:18:40 heavy systemd[1]: monit.service: Succeeded.
#
# 11:21 <twb> This also happens when an apt upgrade of (say) glibc causes monit 
to get "restarted" overnight, and the net result is monit isn't running at all 
:/
# 11:22 <twb> Why is monit doing this?  I can't see anything obvious in the 
logs or the config
# 11:22 <twb> It's my own systemd unit, but it's a fairly boring one [pastebin 
of this file]
# 11:35 <twb> "Monit daemon with pid [%d] stopped\n"  is coming from do_exit()
# 11:39 <twb> do_exit is called by do_default if startdelay (which I have) and 
also state_reboot or Run_Stopped... why would it enter state_reboot or 
Run_Stopped?
# 11:40 <twb> What I suspect is happening is monit is told "stop" and then 1s 
later a new monit is started, but it's still reading "you've been told to stop" 
from the previous monit
# 11:41 <twb> Where does Run.flags get set
# 11:43 <twb> It's only getting Run.flags |= run_stopped *inside* do_exit...
# 11:47 <twb> Logically the only place "you should stop" would be saved is in 
/var/lib/monit/state -- pretty much nothing else is writable
# 11:47 <twb> Where is the serialization code for that blob?
# 11:49 <twb> state.c
# 11:50 <twb>  * Data is stored in binary form in the statefile using the 
following format: <MAGIC><VERSION>{<SERVICE_STATE>}+
# 11:51 <twb> ...i.e. there's nowhere in there storing the Run.flags data at all
# 11:51 <twb> So... what the fuck
[Service]
Restart=always


# Hardening
# We do not need/want monit to restart broken services.
# That is something systemd is capable of doing.
# Therefore we can make monit run with few privileges.
#
# NOTE: DynamicUser=yes requires libnss-systemd, or
#       0 == getpwuid_r(geteuid(), ⋯) will fail and monit will crash.
#
# NOTE: DynamicUser=yes implies NoNewPrivileges=yes.
#       This prevents this setgid privesc:
#          -rwxr-sr-x 1 root shadow 38912 2021-08-27 05:11 /sbin/unix_chkpwd
#       which means you cannot use "allow @coolgroup" (PAM authentication).
#       Using a generic "allow admin:admin" or a htpasswd file should still 
work.
#       This is the exact same class privesc that nginx would need to do PAM 
auth.
#       IMO it is reasonable to take the same approach here that we took for 
gitit KB.
[Service]
User=%p
DynamicUser=yes
StateDirectory=%p
RuntimeDirectory=%p
WorkingDirectory=/run/%p
CacheDirectory=%p
ConfigurationDirectory=%p


# Monit stupidly requires it's entry-point config file
# have the same owner as the monit process (usually root), and
# MUST NOT be group- or world-readable.
# This sort of makes sense when it contains something like
#    "check radius with username root and password swordfish".
#
# But for us, using DynamicUser=yes, it is super annoying.
# To defeat this, trick monit by giving it a config file
# that just says "go read the real config file".
# This works because monit does NOT enforce paranoia for #included files.
#
# PS: monit won't accept this for the same reason (group-readable is not 
allowed):
#
#       set ssl {pemkey:/etc/ssl/private/ssl-cert-snakeoil.key}
#       -rw-r----- root:ssl-cert /etc/ssl/private/ssl-cert-snakeoil.key
#
# UPDATE: with this, "ExecReload=monit reload" is still buggy, AND
#         "sudo -u monit status" can't work.
#         So instead resort to the simpler "just make a mess directly in /etc".
#         The only real downsides are:
#
#           - "git -C /etc log .etckeeper" will churn when monit's UID changes.
#           - you can't just "cat /etc/monitrc" (meh).
#
# [Service]
# UMask=0077
# ExecStartPre=!sh -c         ">/var/lib/monit/monitrc echo include 
/etc/monit/monitrc.real"
# ExecStartPre=!chown -h monit: /var/lib/monit/monitrc
# ExecStartPre=!chmod 0600      /var/lib/monit/monitrc
# BindReadOnlyPaths=/etc/monit/monitrc:/etc/monit/monitrc.real
# BindReadOnlyPaths=/var/lib/monit/monitrc:/etc/monit/monitrc
#
[Service]
ExecStartPre=+chown -h monit: /etc/monit/monitrc
ExecStartPre=+chmod 0600      /etc/monit/monitrc


# systemd-analyze security hardening.
[Service]
CapabilityBoundingSet=
PrivateDevices=yes
ProtectClock=yes
NoNewPrivileges=yes
ProtectKernelLogs=yes
RemoveIPC=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
SystemCallArchitectures=native
MemoryDenyWriteExecute=yes
RestrictNamespaces=yes
RestrictSUIDSGID=yes
ProtectHostname=yes
LockPersonality=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictRealtime=yes
ProtectSystem=strict
# Leave this unlocked so monit does not complain:
#    'heavy' statistic error -- load average data collection failed
#    system statistic error -- cannot get system uptime: No such file or 
directory
# ProcSubset=pid
ProtectHome=yes
PrivateUsers=yes
PrivateTmp=yes
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallFilter=~@resources
UMask=0077


# REDACTED.conf works by doing what is *basically* NRPE, but over SSH (with
# a forced command), instead of a custom protocol.
# To reduce the number of actual SSH connections,
# I told ssh_config to use ControlMaster.
# When I actually look at monit, I see a lot of this sort of thing:
#
#    # Remote host told me to fuck off?
#    status failed (255) -- mux_client_request_session: session request failed: 
Session open refused by peer
#    kex_exchange_identification: read: Connection reset by peer
#    Connection reset by 203.39.179.100 port 22
#
#    # The *control master* told me to fuck off, but when I bypassed it, the 
remote host worked?
#    mux_client_request_session: session request failed: Session open refused 
by peer
#    ControlSocket 
/run/monit/ssh-b3e99525bd607c8f801778c1b5f14b684d01f262.socket already exists, 
disabling multiplexing
#    TCP OK - 0.000 second response time on localhost port 
5432|time=0.000107s;;;0.000000;10.000000
#
# One theory is that if there is no CM yet, and then monit stats all 30 ssh 
checks at once,
#
#   1. ssh A opens the CM socket as CM server
#   2. ssh B opens the CM socket as CM client
#   3. ssh B gives up waiting for CM server
#   4. ssh A is ready to accept CM clients
#
# One straightforward way to avoid this is to just ensure the CM
# server spins up *BEFORE* monit starts.  Let's see if that helps...
#
# UPDATE: this DID NOT help.
#         Further, it caused this whinge from systemd:
#
#             systemd[1]: Stopping Pro-active monitoring utility for unix 
systems...
#             monit[3488964]: Monit daemon with pid [3488964] stopped
#             monit[3488964]: 'heavy' Monit 5.27.2 stopped
#             systemd[1]: monit.service: Succeeded.
#             systemd[1]: Stopped Pro-active monitoring utility for unix 
systems.
#             systemd[1]: monit.service: Consumed 13.846s CPU time.
#             systemd[1]: Starting Pro-active monitoring utility for unix 
systems...
#             ssh[3589446]: OK - load average: 1.68, 2.29, 
2.87|load1=1.680;20.000;30.000;0; load5=2.290;15.000;25.000;0; 
load15=2.870;10.000;20.000;0;
#    -->      systemd[1]: monit.service: Found left-over process 3589446 (ssh) 
in control group while starting unit. Ignoring.
#    -->      systemd[1]: This usually indicates unclean termination of a 
previous run, or service implementation deficiencies.
#             monit[3589447]: Starting Monit 5.27.2 daemon with http interface 
at [*]:2812
#             monit[3589447]: Starting Monit 5.27.2 daemon with http interface 
at [*]:2812
#             systemd[1]: Started Pro-active monitoring utility for unix 
systems.
#             monit[3589449]: 'heavy' Monit 5.27.2 started
#
#ExecStartPre=-/usr/bin/ssh -F /etc/monit/ssh_config REDACTED check_load

[Install]
WantedBy=multi-user.target
# -*-conf-*-
# The upstream default is:
#   https://bitbucket.org/tildeslash/monit/src/master/monitrc
# The (stupid!) Debian changes:
#   
https://sources.debian.org/src/monit/1:5.32.0-1/debian/patches/010_monitrc.patch/

# In Cyber's old Ubuntu 10.04 / Nagios3 config, we had:
#     interval_length 60
#     normal_check_interval 5
#     retry_check_interval 1
#     notification_interval 0
#
# This means:
#
#     * check every 5 minutes when OK;
#     * check every 1 minute when NOT OK;
#     * NEVER send a "it's still down" reminder email.
#
# The upstream monit default cycle is "set daemon 30".
# The Debian monit default cycle is "set daemon 120".
# I think I will set it to 300, to match "when OK, re-try after 5 minutes".
# Monit does not support a faster cycle "when NOT OK".
#
# UPDATE: I commented out "with start delay 60" because it is
#         tangentially implicated in "monit sometimes exit(0)'s
#         immediately on start".  See monit.service for details.
#         --twb, 24 Nov 2022
set daemon 300
#   with start delay 60

# Log to the journal.
# This is the upstream default.
# This is not the Debian default.
set log syslog

# We run monit as an unprivileged user.
# It can write /run/monit/monit.pid.
# It cannot write /run/monit.pid (the default); it will exit(1).
# You cannot tell monit "pidfiles are stupid; don't make one".
set pidfile /run/monit/monit.pid

# This is BASICALLY doing the same job as /etc/machine-id, but
# it is a different syntax and is based on md5 (yuk).
# The default location is ~/.monitid, which
# will not work for a DynamicUser=yes in monit.service.
set idfile /var/lib/monit/id

# This is where monit remembers "was $check failing five minutes ago?"
# The default location is ~/.monit.state, which
# will not work for a DynamicUser=yes in monit.service.
# If you use /var/lib/monit/ it persists across "systemctl restart monit".
# If you use /run/monit/ it is lost across "systemctl restart monit".
set statefile /var/lib/monit/state

# The "event queue" is really an outbound mail spool.
# If an alert (email) fails,
#
#   * if there is no event queue (upstream default),
#     monit just moves on with its life.
#
#   * if there is an event queue (Debian default),
#     monit will re-try to send the email later.
#
# In my experience, 99% of the time, all this achieves is
# AFTER an outage is resolve, you get a flood of "problem!" and "...fixed" 
emails.
# Often the "fixed" emails appear on time and the "problem!" emails arrive 
AFTER a retry delay.
# So it ends up looking like the problem has come back!
# This whole thing is Not Very Interesting, so just skip it.
#
# set eventqueue
#     basedir /var/lib/monit/events
#     slots 100


# We want monit to REPORT errors.
# We don't want monit to try to FIX errors.
# Consider e.g. monit detects alloc is offline.
# Should it restart the firewall, nginx, apache, mysql, uwsgi, or alloc (php)?
# It's bound to restart entirely the wrong thing.
# And if we REALLY wanted that, we would use systemd
# WatchdogSec=60 OnFailure=restart which is better integrated into systemd.
#
# That being the case, explicitly tell monit "DO NOT try to fix problems".
#
# UPDATE: I now understand "mode passive" is something you have to add
#         separately to EVERY CHECK separately, e.g.
#
#            check host    example.com
#                  address example.com
#                  mode passive
#                  if failed port 22 protocol ssh then alert
#                  if failed port 443 protocol https then alert
#                  alert ab...@example.com
#mode passive


# To send emails, talk to smtp://localhost:25/.
# It does not require authentication (msmtpd or postfix).
set mailserver localhost

# Replace "your faithful employee, monit" from message body.
# Replace "Host: heavy" with a clickable link.
# NOTE: Reply-To: support@ is handled in templates/group-amc.
#
# 
https://manpages.debian.org/bullseye-backports/monit/monit.1.en.html#Message_format
#
# The monit default looks like this:
#
#     
https://bitbucket.org/tildeslash/monit/src/release-5-32-0/src/alert.h#lines-32:47
#
# The old nagios format looks like this:
#
#     
https://github.com/NagiosEnterprises/nagioscore/blob/nagios-3.2.0/sample-config/template-object/commands.cfg.in#L26-L38
#     
https://github.com/NagiosEnterprises/nagioscore/blob/nagios-4.4.6/sample-config/template-object/commands.cfg.in#L26-L38
set mail-format {
    from: Monit Availability Monitor <nore...@cyber.com.au>
    subject: Alert: $EVENT $SERVICE
    message: $EVENT $SERVICE

https://monit.cyber.com.au/$SERVICE

Date:        $DATE
Action:      $ACTION
Event:       $EVENT
Check:       $SERVICE

$DESCRIPTION
}


set httpd
  # Listen only on a unix socket; nginx will reverse-proxy it.
  # FIXME: is this going to work?
  #        normally monit.socket would be created by systemd (privileged) and 
then handed to monit as fd 3.
  #        But if monit creates the socket (because monit lacks socket 
activation support),
  #        how can it grant nginx (group www-data) access to it?
  #        If this DOESN'T work, I guess use a localhost-only TCP port (port 
2812 allow localhost).
  #
  # UPDATE: I saw this, and the /run/monit/monit.socket file did not exist.
  #
  #             monit[3388281]: HTTP server -- Could not change unix socket gid 
-- Operation not permitted
  #unixsocket /run/monit/monit.socket
  #gid www-data
  #permission 0660
  # Shitty HTTP (no S) instead?
  port 2812
  allow localhost

  # While monit CAN use PAM authentication ("allow @cyber"),
  # it does so by calling /sbin/unix_chkpwd, which requires set-gid shadow.
  # DynamicUser=yes implies NoNewPrivileges=yes, which blocks all sgid privesc.
  # In other words, PAM auth here requires exactly the same security tradeoffs 
as PAM auth in nginx.
  # Since this web UI is only accessible to people on the LAN/VPN,
  # just have weak "give your username" authentication -- the same as we have 
for the KB.
  # The only purpose of this is to (hopefully) log WHO disabled monitoring of 
check X.
  #
  # NOTE: I think you can also enable/disable a check from the CLI, e.g.
  #
  #           monit --group=amc status
  #           monit unmonitor amc.prisonpc.com
  #           monit monitor   amc.prisonpc.com-check_apache
  #
  # UPDATE: removed.
  #         Nobody benefits (when clicking "unmonitor", the log doesn't 
indicate WHO clicked it), and
  #         conz keeps forgetting and typing in his REAL unix password (i.e. 
monit could learn it).
  #
  # allow conz:""
  # allow ron:""
  # allow twb:""
  # allow cjb:"" readonly
  # allow mattcen:""
  # allow mike:""
  # # This is for monit to check... itself.
  # allow guest:"" readonly


include /etc/monit/conf.d/*
include /etc/monit/conf-enabled/*
# Proxying through nginx really only exists for three reasons:
#   1. monit whinges if the TLS private key isn't EXACTLY the permissions it 
wants.
#   2. so you can browse to https://monit.cyber.com.au/ (without a port number).
#   3. I forget what my third reason was.

server {
    listen 80;
    listen [::]:80;
    server_name monit.cyber.com.au;
    # Serve ACME http-01 challenges directly.
    location /.well-known/ {
        root /var/www/html/;
    }
    # Everything else turns into HTTPS.
    location / {
        return 301 https://$host$request_uri;
    }
}


server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name monit.cyber.com.au;

    ssl_certificate      /etc/letsencrypt-uacme/monit.cyber.com.au/cert.pem;
    ssl_certificate_key  
/etc/letsencrypt-uacme/private/monit.cyber.com.au/key.pem;

    # Serve ACME http-01 challenges directly.
    location /.well-known/ {
        root /var/www/html/;
    }
    # Everything else forwarded to monit.
    location / {
        allow ::1;
        allow 127.0.0.0/8;
        allow 10.194.71.0/24;
        allow 203.7.155.0/24;
        deny all;

        # Can't use this neatly until monit supports "socket activation".
        # Without that, an unprivileged monit cannot "chgrp www-data 
monit.socket".
        #
        # proxy_pass http://unix:/run/monit/monit.socket;
        proxy_pass http://localhost:2812;
    }
}
# -*-conf-*-

Host *
    # Enable ControlMaster (avoid ssh setup/teardown costs for each poll).
    ControlMaster auto
    ControlPath /run/monit/ssh-%C.socket
    ControlPersist 3600
    # Probably overall good?
    Compression yes
    # Passwords are for chumps.
    BatchMode yes
    IdentitiesOnly yes
    IdentityFile /var/lib/monit/ssh.id_ed25519
    UserKnownHostsFile /var/lib/monit/ssh.known_hosts
    # Save me writing "nagios-remote@" in every check.
    User nagios-remote
  NAME                                                        DESCRIPTION       
                                                             EXPOSURE
✗ PrivateNetwork=                                             Service has 
access to the host's network                                            0.5
✓ User=/DynamicUser=                                          Service runs 
under a transient non-root user identity
✓ CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)                Service cannot 
change UID/GID identities/capabilities
✓ CapabilityBoundingSet=~CAP_SYS_ADMIN                        Service has no 
administrator privileges
✓ CapabilityBoundingSet=~CAP_SYS_PTRACE                       Service has no 
ptrace() debugging abilities
✗ RestrictAddressFamilies=~AF_(INET|INET6)                    Service may 
allocate Internet sockets                                               0.3
✓ RestrictNamespaces=~CLONE_NEWUSER                           Service cannot 
create user namespaces
✓ RestrictAddressFamilies=~…                                  Service cannot 
allocate exotic sockets
✓ CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)           Service cannot 
change file ownership/access mode/capabilities
✓ CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)         Service cannot 
override UNIX file/IPC permission checks
✓ CapabilityBoundingSet=~CAP_NET_ADMIN                        Service has no 
network configuration privileges
✓ CapabilityBoundingSet=~CAP_SYS_MODULE                       Service cannot 
load kernel modules
✓ CapabilityBoundingSet=~CAP_SYS_RAWIO                        Service has no 
raw I/O access
✓ CapabilityBoundingSet=~CAP_SYS_TIME                         Service processes 
cannot change the system clock
✗ DeviceAllow=                                                Service has a 
device ACL with some special devices                                  0.1
✗ IPAddressDeny=                                              Service does not 
define an IP address allow list                                    0.2
✓ KeyringMode=                                                Service doesn't 
share key material with other services
✓ NoNewPrivileges=                                            Service processes 
cannot acquire new privileges
✓ NotifyAccess=                                               Service child 
processes cannot alter service state
✓ PrivateDevices=                                             Service has no 
access to hardware devices
✓ PrivateMounts=                                              Service cannot 
install system mounts
✓ PrivateTmp=                                                 Service has no 
access to other software's temporary files
✓ PrivateUsers=                                               Service does not 
have access to other users
✓ ProtectClock=                                               Service cannot 
write to the hardware clock or system clock
✓ ProtectControlGroups=                                       Service cannot 
modify the control group file system
✓ ProtectHome=                                                Service has no 
access to home directories
✓ ProtectKernelLogs=                                          Service cannot 
read from or write to the kernel log ring buffer
✓ ProtectKernelModules=                                       Service cannot 
load or read kernel modules
✓ ProtectKernelTunables=                                      Service cannot 
alter kernel tunables (/proc/sys, …)
✗ ProtectProc=                                                Service has full 
access to process tree (/proc hidepid=)                            0.2
✓ ProtectSystem=                                              Service has 
strict read-only access to the OS file hierarchy
✓ RestrictAddressFamilies=~AF_PACKET                          Service cannot 
allocate packet sockets
✓ RestrictSUIDSGID=                                           SUID/SGID file 
creation by service is restricted
✓ SystemCallArchitectures=                                    Service may 
execute system calls only with native ABI
✓ SystemCallFilter=~@clock                                    System call allow 
list defined for service, and @clock is not included
✓ SystemCallFilter=~@debug                                    System call allow 
list defined for service, and @debug is not included
✓ SystemCallFilter=~@module                                   System call allow 
list defined for service, and @module is not included
✓ SystemCallFilter=~@mount                                    System call allow 
list defined for service, and @mount is not included
✓ SystemCallFilter=~@raw-io                                   System call allow 
list defined for service, and @raw-io is not included
✓ SystemCallFilter=~@reboot                                   System call allow 
list defined for service, and @reboot is not included
✓ SystemCallFilter=~@swap                                     System call allow 
list defined for service, and @swap is not included
✓ SystemCallFilter=~@privileged                               System call allow 
list defined for service, and @privileged is not included
✓ SystemCallFilter=~@resources                                System call allow 
list defined for service, and @resources is not included
✓ AmbientCapabilities=                                        Service process 
does not receive ambient capabilities
✓ CapabilityBoundingSet=~CAP_AUDIT_*                          Service has no 
audit subsystem access
✓ CapabilityBoundingSet=~CAP_KILL                             Service cannot 
send UNIX signals to arbitrary processes
✓ CapabilityBoundingSet=~CAP_MKNOD                            Service cannot 
create device nodes
✓ CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW) Service has no 
elevated networking privileges
✓ CapabilityBoundingSet=~CAP_SYSLOG                           Service has no 
access to kernel logging
✓ CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)              Service has no 
privileges to change resource use parameters
✓ RestrictNamespaces=~CLONE_NEWCGROUP                         Service cannot 
create cgroup namespaces
✓ RestrictNamespaces=~CLONE_NEWIPC                            Service cannot 
create IPC namespaces
✓ RestrictNamespaces=~CLONE_NEWNET                            Service cannot 
create network namespaces
✓ RestrictNamespaces=~CLONE_NEWNS                             Service cannot 
create file system namespaces
✓ RestrictNamespaces=~CLONE_NEWPID                            Service cannot 
create process namespaces
✓ RestrictRealtime=                                           Service realtime 
scheduling access is restricted
✓ SystemCallFilter=~@cpu-emulation                            System call allow 
list defined for service, and @cpu-emulation is not included
✓ SystemCallFilter=~@obsolete                                 System call allow 
list defined for service, and @obsolete is not included
✗ RestrictAddressFamilies=~AF_NETLINK                         Service may 
allocate netlink sockets                                                0.1
✗ RootDirectory=/RootImage=                                   Service runs 
within the host's root directory                                       0.1
✓ SupplementaryGroups=                                        Service has no 
supplementary groups
✓ CapabilityBoundingSet=~CAP_MAC_*                            Service cannot 
adjust SMACK MAC
✓ CapabilityBoundingSet=~CAP_SYS_BOOT                         Service cannot 
issue reboot()
✓ Delegate=                                                   Service does not 
maintain its own delegated control group subtree
✓ LockPersonality=                                            Service cannot 
change ABI personality
✓ MemoryDenyWriteExecute=                                     Service cannot 
create writable executable memory mappings
✓ RemoveIPC=                                                  Service user 
cannot leave SysV IPC objects around
✓ RestrictNamespaces=~CLONE_NEWUTS                            Service cannot 
create hostname namespaces
✓ UMask=                                                      Files created by 
service are accessible only by service's own user by default
✓ CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE                  Service cannot 
mark files immutable
✓ CapabilityBoundingSet=~CAP_IPC_LOCK                         Service cannot 
lock memory into RAM
✓ CapabilityBoundingSet=~CAP_SYS_CHROOT                       Service cannot 
issue chroot()
✓ ProtectHostname=                                            Service cannot 
change system host/domainname
✓ CapabilityBoundingSet=~CAP_BLOCK_SUSPEND                    Service cannot 
establish wake locks
✓ CapabilityBoundingSet=~CAP_LEASE                            Service cannot 
create file leases
✓ CapabilityBoundingSet=~CAP_SYS_PACCT                        Service cannot 
use acct()
✓ CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG                   Service cannot 
issue vhangup()
✓ CapabilityBoundingSet=~CAP_WAKE_ALARM                       Service cannot 
program timers that wake up the system
✗ RestrictAddressFamilies=~AF_UNIX                            Service may 
allocate local sockets                                                  0.1
✗ ProcSubset=                                                 Service has full 
access to non-process /proc files (/proc subset=)                  0.1

→ Overall exposure level for monit.service: 1.3 OK 🙂
#!/usr/bin/python3
import configparser
import os
import shlex
import subprocess
import sys
import syslog
syslog.openlog(ident='nagios-remote', logoption=syslog.LOG_PID)
config = configparser.ConfigParser(
    inline_comment_prefixes='#;',
    interpolation=None)
if os.path.isfile('/etc/nagios-remote.cfg'):
    with open('/etc/nagios-remote.cfg') as f:
        config.read_string(
            source=f.name,
            string='[local]\n' + f.read())
else:
    with open('/etc/nagios/nrpe.cfg') as f:
        config.read_string(
            source=f.name,
            string='[DEFAULT]\n' + f.read())
    with open('/etc/nagios/nrpe_local.cfg') as f:
        config.read_string(
            source=f.name,
            string='[local]\n' + f.read())
if len(sys.argv) != 1:
    raise RuntimeError(
        "Command line arguments are not supported."
        " Use SSH_ORIGINAL_COMMAND instead")
try:
    nrpe_command = os.environ['SSH_ORIGINAL_COMMAND']
    assert nrpe_command.replace('-', '').isidentifier()
    command = config.get('local', 'command[{}]'.format(nrpe_command))
except KeyError:
    print("nagios-remote.py: No SSH_ORIGINAL_COMMAND specified")
    exit(3)
except configparser.NoOptionError:
    print(f"nagios-remote.py: Command {nrpe_command} not recognised")
    exit(3)
except AssertionError:
    print(f"nagios-remote.py: Unsupported command syntax: {nrpe_command}")
    exit(3)
results = subprocess.run(shlex.split(command))
syslog.syslog(
    f'Got {results.returncode} when asked to run {nrpe_command}: {command}')
exit(results.returncode)

Reply via email to