Compare commits
No commits in common. "1faec197a7775a01a988e1745313567e43b2e6bc" and "042103f131a5c35bc4794819d5d964610c118253" have entirely different histories.
1faec197a7
...
042103f131
|
|
@ -159,59 +159,10 @@ lm-sensors: WARN ≥ 65 W, CRIT ≥ 95 W. Add the host to `dev_mod_*` that insta
|
||||||
`lm_sensors` if RAPL is unavailable.
|
`lm_sensors` if RAPL is unavailable.
|
||||||
|
|
||||||
### `usr_mon_logins` (user group)
|
### `usr_mon_logins` (user group)
|
||||||
`Ansipa_Logins` check reporting failed **local** login attempts in the last 24 h
|
`Ansipa_Logins` check reporting SSH login attempts in the last 24 h (successful +
|
||||||
— console/tty, graphical greeters (gdm/sddm/greetd/lightdm): anything
|
|
||||||
authenticating via PAM that isn't sshd or sudo. Reports failed + invalid-user
|
|
||||||
counts, with thresholds. Installed fleet-wide when the group exists; needs
|
|
||||||
`dev_mon_base` on the hosts. SSH activity is tracked separately by
|
|
||||||
`usr_mon_sshlogins`; sudo/access-control denials by `usr_mon_rightsviolations`
|
|
||||||
— both below.
|
|
||||||
|
|
||||||
### `usr_mon_sshlogins` (user group)
|
|
||||||
`Ansipa_SSHLogins` check reporting SSH login attempts in the last 24 h (successful +
|
|
||||||
failed + invalid-user), with thresholds. Installed fleet-wide when the group
|
failed + invalid-user), with thresholds. Installed fleet-wide when the group
|
||||||
exists; needs `dev_mon_base` on the hosts.
|
exists; needs `dev_mon_base` on the hosts.
|
||||||
|
|
||||||
### `usr_mon_rightsviolations` (user group)
|
|
||||||
`Ansipa_RightsViolations` check reporting authorization/access-control denials
|
|
||||||
by an already-authenticated user in the last 24 h — not "who are you" failures
|
|
||||||
(that's the two checks above), but "you are who you say, but that's not
|
|
||||||
allowed": sudo denials (`user NOT in sudoers`, `command not allowed`, incorrect
|
|
||||||
sudo password — works on every distro, sudo always logs these), plus SELinux
|
|
||||||
AVC denials (Fedora/RHEL, only when `getenforce` reports `Enforcing`) and
|
|
||||||
AppArmor denials (Debian/Ubuntu, only if loaded). Each source degrades
|
|
||||||
gracefully when not applicable to the host — this is a best-effort union of
|
|
||||||
what the system already logs, not a guarantee of catching every possible file
|
|
||||||
access denial; watching arbitrary paths would need explicit `auditd` watch
|
|
||||||
rules on specific files, which is a deliberate per-host decision this check
|
|
||||||
does not make for you. WARN ≥ 3 total, CRIT ≥ 10 total. Installed fleet-wide
|
|
||||||
when the group exists; needs `dev_mon_base` on the hosts.
|
|
||||||
|
|
||||||
### `usr_mon_iploc` (user group)
|
|
||||||
`Ansipa_IPLoc` check reporting this device's current public IP and its
|
|
||||||
geolocation (city/region/country/ISP), and **WARNing when the observed country
|
|
||||||
changes** since the last reading — e.g. a device that's normally in one
|
|
||||||
country suddenly appearing in another.
|
|
||||||
|
|
||||||
- Uses two free, no-API-key third-party services: `api.ipify.org` (what's my
|
|
||||||
public IP) and `ip-api.com` (IP → geolocation). The device's public IP is
|
|
||||||
sent to both on every actual lookup — be aware of that before enabling this
|
|
||||||
fleet-wide.
|
|
||||||
- Reports the network **egress** location, not necessarily the device's
|
|
||||||
physical location: a host behind a VPN will show the VPN exit country, and
|
|
||||||
that will (correctly, if noisily) trigger a "country changed" WARN the
|
|
||||||
moment the VPN connects or disconnects.
|
|
||||||
- No hardcoded country blocklist — *any* change from the last-observed
|
|
||||||
country trips a WARN once, then returns to OK on the next reading if the
|
|
||||||
country is stable at the new location. This is intentionally an anomaly
|
|
||||||
("it just moved") signal, not a permanent flag on any particular country.
|
|
||||||
- CheckMK invokes local checks on its own polling cadence (often every
|
|
||||||
minute) — far more often than an external API should be hit. This check
|
|
||||||
self-throttles to one real lookup per 30 minutes and replays its last
|
|
||||||
result in between; the geolocation call additionally only re-fires when
|
|
||||||
the public IP itself has changed.
|
|
||||||
- Installed fleet-wide when the group exists; needs `dev_mon_base` on the hosts.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Software-installer policies (`dev_pkg_*`, `dev_mod_*`, `dev_fp_*`)
|
## Software-installer policies (`dev_pkg_*`, `dev_mod_*`, `dev_fp_*`)
|
||||||
|
|
@ -290,50 +241,6 @@ the live `policies.d/*.sh` files directly on the share — changes take effect
|
||||||
fleet-wide on the next 30-min enforcer tick, no redeploy needed. Credential in
|
fleet-wide on the next 30-min enforcer tick, no redeploy needed. Credential in
|
||||||
the group description (auto-set by `ansipa-smb-setup.sh`).
|
the group description (auto-set by `ansipa-smb-setup.sh`).
|
||||||
|
|
||||||
### `usr_ctl_dnshostfile`
|
|
||||||
Grants members write access to `/etc/hosts` via a POSIX ACL
|
|
||||||
(`setfacl -m u:<user>:rw /etc/hosts`) on every enrolled host. Unlike the other
|
|
||||||
`usr_*` grants above, this doesn't rely on runtime SSSD/PAM resolution — a
|
|
||||||
filesystem ACL can't check group membership dynamically at the point of use,
|
|
||||||
so the enforcer computes the group's actual member list (`ipa group-show
|
|
||||||
usr_ctl_dnshostfile --all`) and re-applies it every tick, revoking the ACL
|
|
||||||
entry for anyone who has left.
|
|
||||||
|
|
||||||
- **Scope:** `/etc/hosts` only — deliberately not `/etc/resolv.conf`, which on
|
|
||||||
systemd-resolved systems is usually a symlink to a tmpfs stub regenerated on
|
|
||||||
boot/network changes, so a static ACL there would silently vanish.
|
|
||||||
- **Requires:** the `acl` package (`setfacl`/`getfacl`) on the client.
|
|
||||||
- **Security note:** this is a real grant, not a cosmetic one — a member can
|
|
||||||
redirect any hostname to any IP *on that machine*, enough to locally spoof a
|
|
||||||
login page or update server. Grant deliberately.
|
|
||||||
|
|
||||||
### `usr_ctl_netman` / `usr_ctl_wifi`
|
|
||||||
Grant members the ability to modify this device's NetworkManager connections
|
|
||||||
via NetworkManager's own per-connection `connection.permissions` mechanism —
|
|
||||||
NOT a blanket sudo/root grant. NetworkManager already lets a connection's
|
|
||||||
designated non-root "permitted user" modify/activate that connection without
|
|
||||||
a password (`org.freedesktop.NetworkManager.settings.modify.own`, allowed for
|
|
||||||
local users by default on every mainstream distro's polkit rules); these
|
|
||||||
policies just add the member to that list.
|
|
||||||
|
|
||||||
- **`usr_ctl_netman`** — every **non-Wi-Fi** connection (ethernet, bridge,
|
|
||||||
bond, VLAN, …): IP address, gateway, DNS, routes. Wi-Fi connections are left
|
|
||||||
completely untouched.
|
|
||||||
- **`usr_ctl_wifi`** — every **Wi-Fi** connection only: SSID, security/PSK,
|
|
||||||
etc. Wired/other connections are left completely untouched.
|
|
||||||
- A user can be in either, both, or neither — the two are kept deliberately
|
|
||||||
separate rather than one combined "network admin" group.
|
|
||||||
- Like `usr_ctl_dnshostfile`, membership is resolved to concrete usernames
|
|
||||||
(`ipa group-show ... --all`) and re-applied every enforcer tick, since
|
|
||||||
NetworkManager's connection permissions are static profile metadata, not
|
|
||||||
something checkable dynamically at the point of use.
|
|
||||||
- **Requires:** NetworkManager (`nmcli`) on the client.
|
|
||||||
- Applies to connections that exist at enforcement time; a brand-new
|
|
||||||
connection created afterwards is picked up on the next 30-min tick. Users
|
|
||||||
can already create/manage their own new connections by default (standard
|
|
||||||
NetworkManager multi-user behavior) — these policies are specifically about
|
|
||||||
granting access to existing, previously root-owned connections.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## BaseUser sync (`BaseUser` group)
|
## BaseUser sync (`BaseUser` group)
|
||||||
|
|
|
||||||
|
|
@ -18,38 +18,6 @@ COLOR_RED=F50505 # Red — danger indicators, alerts
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Terminal ANSI Palette
|
|
||||||
|
|
||||||
The 5 base colours above cover UI accents, but terminals (kitty, Alacritty, Ghostty) also need a full 16-colour ANSI palette. The "CyberQueer" terminal theme extends the base palette with neon green/yellow/magenta/cyan tints kept in the same violet/crimson family, so `ls`, `git`, and syntax highlighting stay legible instead of clashing.
|
|
||||||
|
|
||||||
Defined in `desktopenvs/hyprdrive/kitty/themes/cyberqueer.conf` (canonical source) and mirrored into every kitty and Alacritty config in the repo:
|
|
||||||
|
|
||||||
| Slot | Name | Normal | Bright |
|
|
||||||
|------|------|--------|--------|
|
|
||||||
| 0/8 | black | `#1A1A1A` | `#4B3A55` |
|
|
||||||
| 1/9 | red | `#E40046` | `#F50505` |
|
|
||||||
| 2/10 | green | `#33E6A0` | `#5CFFC2` |
|
|
||||||
| 3/11 | yellow | `#E8B23D` | `#FFD873` |
|
|
||||||
| 4/12 | blue | `#5018DD` | `#8C6FFF` |
|
|
||||||
| 5/13 | magenta | `#A6178C` | `#FF5FC0` |
|
|
||||||
| 6/14 | cyan | `#34C6C6` | `#6FF5F0` |
|
|
||||||
| 7/15 | white | `#D6ABAB` | `#F2DEDE` |
|
|
||||||
|
|
||||||
Plus the fixed UI slots:
|
|
||||||
|
|
||||||
| Role | Hex |
|
|
||||||
|------|-----|
|
|
||||||
| Foreground | `#D6ABAB` |
|
|
||||||
| Background | `#1A1A1A` |
|
|
||||||
| Cursor | `#E40046` |
|
|
||||||
| Cursor text | `#5018DD` |
|
|
||||||
| Selection background | `#5018DD` |
|
|
||||||
| Selection foreground | `#E40046` |
|
|
||||||
|
|
||||||
This palette is deployed identically to kitty (`kitty/themes/cyberqueer.conf`, `kitty/current-theme.conf`), Alacritty (`alacritty/alacritty.toml`, `[colors]` tables), and Ghostty (`ghostty/config`) across every desktop environment, so switching terminal emulators doesn't change the colours you see.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Applying the Theme
|
## Applying the Theme
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -89,8 +57,6 @@ On a fresh install where configs have been copied but no state file exists yet,
|
||||||
| `kitty/current-theme.conf` | Terminal colour palette |
|
| `kitty/current-theme.conf` | Terminal colour palette |
|
||||||
| `kitty/kitty.conf` | Terminal background & accents |
|
| `kitty/kitty.conf` | Terminal background & accents |
|
||||||
| `kitty/themes/cyberqueer.conf` | Kitty colour scheme definition |
|
| `kitty/themes/cyberqueer.conf` | Kitty colour scheme definition |
|
||||||
| `alacritty/alacritty.toml` | Alacritty terminal colour palette |
|
|
||||||
| `ghostty/config` | Ghostty terminal colour palette |
|
|
||||||
| `waybar/style.css` | Top bar widget colours |
|
| `waybar/style.css` | Top bar widget colours |
|
||||||
| `wofi/style.css` | App launcher colours |
|
| `wofi/style.css` | App launcher colours |
|
||||||
| `walker/themes/cyberqueer.css` | Walker launcher theme |
|
| `walker/themes/cyberqueer.css` | Walker launcher theme |
|
||||||
|
|
|
||||||
|
|
@ -77,48 +77,10 @@
|
||||||
# automatically: commit and push (GPG-signed) to the ansipa-policy
|
# automatically: commit and push (GPG-signed) to the ansipa-policy
|
||||||
# git server, which ansipa-pull-apply.sh verifies and distributes
|
# git server, which ansipa-pull-apply.sh verifies and distributes
|
||||||
# fleet-wide. See docs/md/freeipa-ansible.md.
|
# fleet-wide. See docs/md/freeipa-ansible.md.
|
||||||
# usr_ctl_dnshostfile Grants members write access to /etc/hosts via a POSIX ACL
|
# usr_mon_logins (CheckMK) Local check for SSH login attempts in the last 24 h:
|
||||||
# (setfacl). Unlike the grants above, membership can't be checked
|
|
||||||
# dynamically at the point of use (a filesystem ACL isn't a
|
|
||||||
# runtime SSSD/PAM check), so the enforcer resolves the group's
|
|
||||||
# actual member list every tick and revokes the ACL entry for
|
|
||||||
# anyone who has left. Requires the 'acl' package.
|
|
||||||
# usr_ctl_netman Grants members modify access to this host's NON-Wi-Fi
|
|
||||||
# NetworkManager connections (IP/gateway/DNS/routes) via
|
|
||||||
# NetworkManager's own connection.permissions mechanism — NOT sudo.
|
|
||||||
# Wi-Fi connections are left completely untouched (see
|
|
||||||
# usr_ctl_wifi). Requires NetworkManager (nmcli).
|
|
||||||
# usr_ctl_wifi Grants members modify access to this host's Wi-Fi
|
|
||||||
# NetworkManager connections only (SSID/security) — the mirror of
|
|
||||||
# usr_ctl_netman; wired/other connections are untouched. A user
|
|
||||||
# can be in either, both, or neither. Requires NetworkManager (nmcli).
|
|
||||||
# usr_mon_logins (CheckMK) Local check for failed LOCAL login attempts in the
|
|
||||||
# last 24 h — console/tty, graphical greeters: anything via PAM
|
|
||||||
# that isn't sshd or sudo (those are usr_mon_sshlogins and
|
|
||||||
# usr_mon_rightsviolations, below).
|
|
||||||
# Reports failed + invalid-user counts with thresholds.
|
|
||||||
# WARN: ≥10 failed or ≥5 invalid; CRIT: ≥50 failed or ≥20 invalid.
|
|
||||||
# Installed fleet-wide when the group exists; requires dev_mon_base.
|
|
||||||
# usr_mon_sshlogins (CheckMK) Local check for SSH login attempts in the last 24 h:
|
|
||||||
# successful + failed + invalid-user counts with thresholds.
|
# successful + failed + invalid-user counts with thresholds.
|
||||||
# WARN: ≥10 failed or ≥5 invalid; CRIT: ≥50 failed or ≥20 invalid.
|
# WARN: ≥10 failed or ≥5 invalid; CRIT: ≥50 failed or ≥20 invalid.
|
||||||
# Installed fleet-wide when the group exists; requires dev_mon_base.
|
# Installed fleet-wide when the group exists; requires dev_mon_base.
|
||||||
# usr_mon_rightsviolations (CheckMK) Local check for authorization/access-control denials
|
|
||||||
# by an already-authenticated user in the last 24 h: sudo denials
|
|
||||||
# (every distro), plus SELinux AVC denials (Fedora/RHEL, if
|
|
||||||
# enforcing) and AppArmor denials (Debian/Ubuntu, if loaded) —
|
|
||||||
# each source degrades gracefully when not applicable to the host.
|
|
||||||
# WARN: ≥3 total; CRIT: ≥10 total.
|
|
||||||
# Installed fleet-wide when the group exists; requires dev_mon_base.
|
|
||||||
# usr_mon_iploc (CheckMK) Local check reporting this device's current public IP
|
|
||||||
# and geolocation (via api.ipify.org + ip-api.com — both free,
|
|
||||||
# no-API-key third-party services); WARNs when the observed
|
|
||||||
# country changes since the last reading (e.g. a device normally
|
|
||||||
# in one country suddenly showing up elsewhere). Reports the
|
|
||||||
# network EGRESS location — a VPN's exit country, not necessarily
|
|
||||||
# the device's physical location. Self-throttles to one real
|
|
||||||
# lookup per 30 min regardless of how often CheckMK polls.
|
|
||||||
# Installed fleet-wide when the group exists; requires dev_mon_base.
|
|
||||||
#
|
#
|
||||||
# Notes:
|
# Notes:
|
||||||
# - Install scan tools first: add the host to dev_mod_anti-malware.
|
# - Install scan tools first: add the host to dev_mod_anti-malware.
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,6 @@
|
||||||
# usr_smb_rw_<name> Mount read-write Samba share ~/‹name› for members (rw beats r if both)
|
# usr_smb_rw_<name> Mount read-write Samba share ~/‹name› for members (rw beats r if both)
|
||||||
# usr_smb_adm_policystore Mount the policy store at ~/policystore (rw) for admin members;
|
# usr_smb_adm_policystore Mount the policy store at ~/policystore (rw) for admin members;
|
||||||
# credential in IPA group description (set by ansipa-smb-setup.sh)
|
# credential in IPA group description (set by ansipa-smb-setup.sh)
|
||||||
# usr_ctl_dnshostfile Grant write access to /etc/hosts via a POSIX ACL (setfacl).
|
|
||||||
# Membership resolved to concrete usernames every tick (a
|
|
||||||
# filesystem ACL can't be checked dynamically). Requires 'acl'.
|
|
||||||
# usr_ctl_netman Grant modify access to non-Wi-Fi NetworkManager connections
|
|
||||||
# (IP/gateway/DNS/routes) via connection.permissions — not sudo.
|
|
||||||
# Wi-Fi is untouched (see usr_ctl_wifi). Requires nmcli.
|
|
||||||
# usr_ctl_wifi Grant modify access to Wi-Fi NetworkManager connections only
|
|
||||||
# (SSID/security) — the mirror of usr_ctl_netman. Requires nmcli.
|
|
||||||
#
|
#
|
||||||
# CheckMK monitoring policies (device host-groups, unless noted):
|
# CheckMK monitoring policies (device host-groups, unless noted):
|
||||||
# dev_mon_base Install CheckMK agent; register host in CMK; check: installed packages.
|
# dev_mon_base Install CheckMK agent; register host in CMK; check: installed packages.
|
||||||
|
|
@ -51,18 +43,8 @@
|
||||||
# dev_mon_timeshift Local check for most recent Timeshift snapshot age.
|
# dev_mon_timeshift Local check for most recent Timeshift snapshot age.
|
||||||
# WARN if >5 days, CRIT if >10 days, CRIT if no snapshots found.
|
# WARN if >5 days, CRIT if >10 days, CRIT if no snapshots found.
|
||||||
# dev_mon_power Local check for CPU package TDP/power via Intel RAPL or lm-sensors.
|
# dev_mon_power Local check for CPU package TDP/power via Intel RAPL or lm-sensors.
|
||||||
# usr_mon_logins (User group) Local check for failed LOCAL login attempts in
|
# usr_mon_logins (User group) Local check for SSH login attempts in last 24h.
|
||||||
# last 24h (console/tty/greeter — anything via PAM that isn't
|
|
||||||
# sshd or sudo). Reports: failed + invalid-user counts with thresholds.
|
|
||||||
# usr_mon_sshlogins (User group) Local check for SSH login attempts in last 24h.
|
|
||||||
# Reports: successful + failed + invalid-user counts with thresholds.
|
# Reports: successful + failed + invalid-user counts with thresholds.
|
||||||
# usr_mon_rightsviolations (User group) Local check for authorization/access-control denials
|
|
||||||
# in last 24h: sudo denials (every distro) + SELinux AVC denials
|
|
||||||
# (if enforcing) + AppArmor denials (if loaded).
|
|
||||||
# usr_mon_iploc (User group) Local check for this device's public IP + geolocation
|
|
||||||
# (api.ipify.org + ip-api.com); WARNs on a country change since the
|
|
||||||
# last reading. Reports network egress location, not necessarily the
|
|
||||||
# device's physical location. Self-throttled to one lookup/30min.
|
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
# - Host enrolled in FreeIPA (sssd + ipa CLI available)
|
# - Host enrolled in FreeIPA (sssd + ipa CLI available)
|
||||||
|
|
|
||||||
|
|
@ -152,20 +152,6 @@ _smb_parse_cred() {
|
||||||
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]}"
|
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Group member list helper ──────────────────────────────────────────────────
|
|
||||||
# Returns newline-separated usernames belonging to the given IPA user group.
|
|
||||||
# Used by capability-grant policies (usr_ctl_*) that apply to concrete user
|
|
||||||
# accounts rather than relying on runtime SSSD/PAM resolution — filesystem
|
|
||||||
# ACLs and NetworkManager connection permissions are static profile metadata,
|
|
||||||
# not something that can be checked dynamically at the point of use the way a
|
|
||||||
# sudo rule or PATH wrapper can, so the enforcer must compute and re-apply the
|
|
||||||
# member set on every tick.
|
|
||||||
_ipa_group_member_users() {
|
|
||||||
ipa group-show "$1" --all 2>/dev/null \
|
|
||||||
| awk -F': ' '/Member users:/{print $2; exit}' \
|
|
||||||
| tr ',' '\n' | sed 's/^ *//; s/ *$//' | grep -v '^$' || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# NOTE: this file previously had an _ansipa_sync_policystore() here, which
|
# NOTE: this file previously had an _ansipa_sync_policystore() here, which
|
||||||
# synced *.sh files from the ansipa-policystore SMB share directly into
|
# synced *.sh files from the ansipa-policystore SMB share directly into
|
||||||
# $POLICY_DIR with no integrity check before they were sourced as root. That
|
# $POLICY_DIR with no integrity check before they were sourced as root. That
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,7 @@
|
||||||
# dev_mon_base install agent, register host, installed-packages check
|
# dev_mon_base install agent, register host, installed-packages check
|
||||||
# dev_mon_timeshift Timeshift snapshot age check
|
# dev_mon_timeshift Timeshift snapshot age check
|
||||||
# dev_mon_power CPU package power via RAPL / lm-sensors
|
# dev_mon_power CPU package power via RAPL / lm-sensors
|
||||||
# usr_mon_logins failed LOCAL login audit (console/tty/greeter — anything
|
# usr_mon_logins SSH login audit check (user group, but requires dev_mon_base)
|
||||||
# via PAM that isn't sshd or sudo); user group, requires dev_mon_base
|
|
||||||
# usr_mon_sshlogins SSH login audit check (user group, but requires dev_mon_base)
|
|
||||||
# usr_mon_rightsviolations sudo denials + SELinux/AppArmor access-control
|
|
||||||
# denials (user group, but requires dev_mon_base)
|
|
||||||
# usr_mon_iploc public IP + geolocation, flags a country change
|
|
||||||
# (user group, but requires dev_mon_base)
|
|
||||||
# (ClamAV scan reporting lives in its own policy, dev_mon_clamscan, which runs
|
# (ClamAV scan reporting lives in its own policy, dev_mon_clamscan, which runs
|
||||||
# the scan and writes the Ansipa_ClamScan local check the agent here collects.)
|
# the scan and writes the Ansipa_ClamScan local check the agent here collects.)
|
||||||
# Revert: deregisters the host and removes the agent socket when dev_mon_base leaves.
|
# Revert: deregisters the host and removes the agent socket when dev_mon_base leaves.
|
||||||
|
|
@ -290,40 +284,9 @@ else
|
||||||
fi'
|
fi'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── usr_mon_logins: failed LOCAL login audit (requires dev_mon_base agent) ─
|
# ── usr_mon_logins: SSH login audit (requires dev_mon_base agent) ─────
|
||||||
# "Local" = anything authenticating via PAM that ISN'T sshd — console/tty
|
|
||||||
# (login), graphical greeters (gdm/sddm/greetd/lightdm). SSH activity is
|
|
||||||
# tracked separately by usr_mon_sshlogins; sudo/access-control denials by
|
|
||||||
# usr_mon_rightsviolations — both excluded here to keep the three clean.
|
|
||||||
if _mon_usr_active "logins"; then
|
if _mon_usr_active "logins"; then
|
||||||
_cmk_write_check "ansipa_logins" '#!/bin/bash
|
_cmk_write_check "ansipa_logins" '#!/bin/bash
|
||||||
if command -v journalctl &>/dev/null && journalctl --since "24 hours ago" -q &>/dev/null; then
|
|
||||||
LINES=$(journalctl --since "24 hours ago" --no-pager -q 2>/dev/null \
|
|
||||||
| grep -iE "authentication failure|failed login" | grep -viE "sshd|sudo")
|
|
||||||
else
|
|
||||||
LINES=$(
|
|
||||||
{ grep "$(date "+%b %e")" /var/log/secure /var/log/auth.log 2>/dev/null
|
|
||||||
grep "$(date -d yesterday "+%b %e" 2>/dev/null || true)" /var/log/secure /var/log/auth.log 2>/dev/null
|
|
||||||
} | grep -iE "authentication failure|failed login" | grep -viE "sshd|sudo"
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
FAILED=0
|
|
||||||
[[ -n "$LINES" ]] && FAILED=$(grep -c . <<< "$LINES")
|
|
||||||
INVALID=$(grep -icE "unknown user|user unknown" <<< "$LINES" 2>/dev/null || echo 0)
|
|
||||||
PERF="failed=${FAILED};10;50 invalid=${INVALID};5;20"
|
|
||||||
MSG="Local failed logins 24h: ${FAILED} failed / ${INVALID} invalid user"
|
|
||||||
if [[ "$FAILED" -ge 50 ]] || [[ "$INVALID" -ge 20 ]]; then
|
|
||||||
echo "2 Ansipa_Logins ${PERF} HIGH ACTIVITY — ${MSG}"
|
|
||||||
elif [[ "$FAILED" -ge 10 ]] || [[ "$INVALID" -ge 5 ]]; then
|
|
||||||
echo "1 Ansipa_Logins ${PERF} ELEVATED — ${MSG}"
|
|
||||||
else
|
|
||||||
echo "0 Ansipa_Logins ${PERF} ${MSG}"
|
|
||||||
fi'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── usr_mon_sshlogins: SSH login audit (requires dev_mon_base agent) ──
|
|
||||||
if _mon_usr_active "sshlogins"; then
|
|
||||||
_cmk_write_check "ansipa_sshlogins" '#!/bin/bash
|
|
||||||
count_journal() {
|
count_journal() {
|
||||||
local pat="$1"
|
local pat="$1"
|
||||||
journalctl -u sshd --since "24 hours ago" --no-pager -q 2>/dev/null \
|
journalctl -u sshd --since "24 hours ago" --no-pager -q 2>/dev/null \
|
||||||
|
|
@ -345,138 +308,13 @@ else
|
||||||
INVALID=$(count_secure "sshd.*Invalid user")
|
INVALID=$(count_secure "sshd.*Invalid user")
|
||||||
fi
|
fi
|
||||||
PERF="successful=${ACCEPTED};; failed=${FAILED};10;50 invalid=${INVALID};5;20"
|
PERF="successful=${ACCEPTED};; failed=${FAILED};10;50 invalid=${INVALID};5;20"
|
||||||
MSG="SSH logins 24h: ${ACCEPTED} ok / ${FAILED} failed / ${INVALID} invalid user"
|
MSG="Logins 24h: ${ACCEPTED} ok / ${FAILED} failed / ${INVALID} invalid user"
|
||||||
if [[ "$FAILED" -ge 50 ]] || [[ "$INVALID" -ge 20 ]]; then
|
if [[ "$FAILED" -ge 50 ]] || [[ "$INVALID" -ge 20 ]]; then
|
||||||
echo "2 Ansipa_SSHLogins ${PERF} HIGH ACTIVITY — ${MSG}"
|
echo "2 Ansipa_Logins ${PERF} HIGH ACTIVITY — ${MSG}"
|
||||||
elif [[ "$FAILED" -ge 10 ]] || [[ "$INVALID" -ge 5 ]]; then
|
elif [[ "$FAILED" -ge 10 ]] || [[ "$INVALID" -ge 5 ]]; then
|
||||||
echo "1 Ansipa_SSHLogins ${PERF} ELEVATED — ${MSG}"
|
echo "1 Ansipa_Logins ${PERF} ELEVATED — ${MSG}"
|
||||||
else
|
else
|
||||||
echo "0 Ansipa_SSHLogins ${PERF} ${MSG}"
|
echo "0 Ansipa_Logins ${PERF} ${MSG}"
|
||||||
fi'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── usr_mon_rightsviolations: authorization/access-control denials ────
|
|
||||||
# An already-authenticated user attempting something they're not allowed
|
|
||||||
# to do: sudo denials (works on every distro — sudo always logs these),
|
|
||||||
# plus SELinux AVC denials (Fedora/RHEL, only if enforcing) and AppArmor
|
|
||||||
# denials (Debian/Ubuntu, only if loaded) via the journal. Each source
|
|
||||||
# degrades gracefully when not applicable to this host. This is a
|
|
||||||
# best-effort union of what's already logged system-wide, not a
|
|
||||||
# guarantee of catching every possible file-access denial — watching
|
|
||||||
# arbitrary paths needs auditd watches on specific files, which is a
|
|
||||||
# deliberate per-host decision this check does not assume for you.
|
|
||||||
if _mon_usr_active "rightsviolations"; then
|
|
||||||
_cmk_write_check "ansipa_rightsviolations" '#!/bin/bash
|
|
||||||
if command -v journalctl &>/dev/null && journalctl --since "24 hours ago" -q &>/dev/null; then
|
|
||||||
SUDO_LINES=$(journalctl --since "24 hours ago" --no-pager -q 2>/dev/null | grep -i "sudo")
|
|
||||||
else
|
|
||||||
SUDO_LINES=$(
|
|
||||||
{ grep "$(date "+%b %e")" /var/log/secure /var/log/auth.log 2>/dev/null
|
|
||||||
grep "$(date -d yesterday "+%b %e" 2>/dev/null || true)" /var/log/secure /var/log/auth.log 2>/dev/null
|
|
||||||
} | grep -i "sudo"
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
SUDO_DENIED=0
|
|
||||||
[[ -n "$SUDO_LINES" ]] && SUDO_DENIED=$(grep -icE \
|
|
||||||
"not in the sudoers file|command not allowed|incorrect password attempt|authentication failure" \
|
|
||||||
<<< "$SUDO_LINES")
|
|
||||||
|
|
||||||
SELINUX_DENIED=0
|
|
||||||
if command -v getenforce &>/dev/null && [[ "$(getenforce 2>/dev/null)" == "Enforcing" ]] \
|
|
||||||
&& command -v journalctl &>/dev/null && journalctl --since "24 hours ago" -q &>/dev/null; then
|
|
||||||
SELINUX_DENIED=$(journalctl --since "24 hours ago" --no-pager -q 2>/dev/null | grep -c "avc: denied")
|
|
||||||
fi
|
|
||||||
|
|
||||||
APPARMOR_DENIED=0
|
|
||||||
if command -v journalctl &>/dev/null && journalctl --since "24 hours ago" -q &>/dev/null; then
|
|
||||||
APPARMOR_DENIED=$(journalctl --since "24 hours ago" --no-pager -q 2>/dev/null | grep -c "apparmor=\"DENIED\"")
|
|
||||||
fi
|
|
||||||
|
|
||||||
TOTAL=$(( SUDO_DENIED + SELINUX_DENIED + APPARMOR_DENIED ))
|
|
||||||
PERF="sudo_denied=${SUDO_DENIED} selinux_denied=${SELINUX_DENIED} apparmor_denied=${APPARMOR_DENIED} total=${TOTAL};3;10"
|
|
||||||
MSG="Rights violations 24h: ${SUDO_DENIED} sudo / ${SELINUX_DENIED} selinux / ${APPARMOR_DENIED} apparmor"
|
|
||||||
if [[ "$TOTAL" -ge 10 ]]; then
|
|
||||||
echo "2 Ansipa_RightsViolations ${PERF} HIGH ACTIVITY — ${MSG}"
|
|
||||||
elif [[ "$TOTAL" -ge 3 ]]; then
|
|
||||||
echo "1 Ansipa_RightsViolations ${PERF} ELEVATED — ${MSG}"
|
|
||||||
else
|
|
||||||
echo "0 Ansipa_RightsViolations ${PERF} ${MSG}"
|
|
||||||
fi'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── usr_mon_iploc: public IP + geolocation, flag a country change ─────
|
|
||||||
# NOTE: this reports where the device's NETWORK EGRESS appears to be —
|
|
||||||
# if the host is behind a VPN, that is the VPN exit location, not
|
|
||||||
# necessarily the device's physical location. Uses two free,
|
|
||||||
# no-API-key third-party services (api.ipify.org for the IP,
|
|
||||||
# ip-api.com for geolocation) — the device's public IP is sent to
|
|
||||||
# both on every actual lookup; see docs/md/ansipa-administration.md.
|
|
||||||
# CheckMK invokes local checks on its own polling cadence (often every
|
|
||||||
# 1 min), far more often than external services should be hit, so this
|
|
||||||
# throttles itself to one real lookup per THROTTLE_SECONDS and replays
|
|
||||||
# its last result in between. The geolocation call additionally only
|
|
||||||
# re-fires when the public IP itself has changed.
|
|
||||||
if _mon_usr_active "iploc"; then
|
|
||||||
_cmk_write_check "ansipa_iploc" '#!/bin/bash
|
|
||||||
STATE_DIR="/var/lib/ansipa-policies"
|
|
||||||
TS_STATE="$STATE_DIR/iploc-last-ts"
|
|
||||||
IP_STATE="$STATE_DIR/iploc-last-ip"
|
|
||||||
LOC_STATE="$STATE_DIR/iploc-last-loc"
|
|
||||||
OUT_STATE="$STATE_DIR/iploc-last-output"
|
|
||||||
THROTTLE_SECONDS=1800
|
|
||||||
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
||||||
|
|
||||||
NOW=$(date +%s)
|
|
||||||
LAST_TS=$(cat "$TS_STATE" 2>/dev/null || echo 0)
|
|
||||||
if [[ -f "$OUT_STATE" ]] && (( NOW - LAST_TS < THROTTLE_SECONDS )); then
|
|
||||||
cat "$OUT_STATE"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
_emit() { echo "$1" | tee "$OUT_STATE" >/dev/null; echo "$1"; echo "$NOW" > "$TS_STATE"; }
|
|
||||||
|
|
||||||
PUB_IP=$(curl -fsS -m 5 https://api.ipify.org 2>/dev/null \
|
|
||||||
|| curl -fsS -m 5 https://ifconfig.me/ip 2>/dev/null \
|
|
||||||
|| curl -fsS -m 5 https://icanhazip.com 2>/dev/null | tr -d "[:space:]")
|
|
||||||
|
|
||||||
if [[ -z "$PUB_IP" ]]; then
|
|
||||||
_emit "3 Ansipa_IPLoc - Could not determine public IP (no internet / lookup services unreachable)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
LAST_IP=$(cat "$IP_STATE" 2>/dev/null || echo "")
|
|
||||||
LAST_LOC=$(cat "$LOC_STATE" 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [[ "$PUB_IP" == "$LAST_IP" ]] && [[ -n "$LAST_LOC" ]]; then
|
|
||||||
CUR_LOC="$LAST_LOC"
|
|
||||||
else
|
|
||||||
GEO=$(curl -fsS -m 5 "http://ip-api.com/line/${PUB_IP}?fields=status,country,countryCode,regionName,city,isp" 2>/dev/null)
|
|
||||||
if [[ -z "$GEO" ]]; then
|
|
||||||
_emit "3 Ansipa_IPLoc - Public IP is ${PUB_IP}, geolocation lookup failed"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
mapfile -t _G <<< "$GEO"
|
|
||||||
if [[ "${_G[0]:-}" != "success" ]]; then
|
|
||||||
_emit "3 Ansipa_IPLoc - Public IP is ${PUB_IP}, geolocation lookup returned: ${_G[0]:-unknown}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
CUR_LOC="${_G[1]:-unknown}|${_G[2]:-??}|${_G[3]:-}|${_G[4]:-}|${_G[5]:-}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
IFS="|" read -r CUR_COUNTRY CUR_CC CUR_REGION CUR_CITY CUR_ISP <<< "$CUR_LOC"
|
|
||||||
|
|
||||||
LAST_CC=""
|
|
||||||
if [[ -n "$LAST_LOC" ]] && [[ "$LAST_LOC" != "$CUR_LOC" ]]; then
|
|
||||||
IFS="|" read -r LAST_COUNTRY LAST_CC _ _ _ <<< "$LAST_LOC"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$PUB_IP" > "$IP_STATE"
|
|
||||||
echo "$CUR_LOC" > "$LOC_STATE"
|
|
||||||
|
|
||||||
if [[ -n "$LAST_CC" ]] && [[ "$LAST_CC" != "$CUR_CC" ]]; then
|
|
||||||
_emit "1 Ansipa_IPLoc - COUNTRY CHANGED: ${LAST_COUNTRY} -> ${CUR_COUNTRY} (now ${CUR_CITY}, ${CUR_REGION} - public IP ${PUB_IP}, ${CUR_ISP})"
|
|
||||||
else
|
|
||||||
_emit "0 Ansipa_IPLoc - ${CUR_CITY}, ${CUR_REGION}, ${CUR_COUNTRY} (${CUR_ISP}) - public IP ${PUB_IP}"
|
|
||||||
fi'
|
fi'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# policy: usr_ctl_dnshostfile — grant specific IPA users write access to
|
|
||||||
# /etc/hosts via a POSIX ACL. Default (no membership) leaves /etc/hosts at
|
|
||||||
# its normal root-only permissions — membership is what GRANTS the right,
|
|
||||||
# it does not restrict anything for non-members.
|
|
||||||
#
|
|
||||||
# Scope: /etc/hosts only, deliberately not /etc/resolv.conf. On systems using
|
|
||||||
# systemd-resolved, /etc/resolv.conf is usually a symlink to a stub file on
|
|
||||||
# tmpfs that gets regenerated on boot/network changes, so a static ACL there
|
|
||||||
# would silently vanish; covering it properly would mean resolving and
|
|
||||||
# re-applying to whatever the symlink currently targets on every run, which
|
|
||||||
# is a deliberate follow-up rather than something folded in here silently.
|
|
||||||
#
|
|
||||||
# Requires: the 'acl' package (setfacl/getfacl) on the client.
|
|
||||||
# Security note: this is a real grant, not a cosmetic one — a member can
|
|
||||||
# redirect any hostname to any IP on this machine (local DNS override), which
|
|
||||||
# is enough to locally spoof a login page or update server. Grant deliberately.
|
|
||||||
|
|
||||||
HOSTS_FILE="/etc/hosts"
|
|
||||||
DNSHOST_STATE="$STATE_DIR/dnshostfile-acl-users"
|
|
||||||
[[ -f "$DNSHOST_STATE" ]] || touch "$DNSHOST_STATE"
|
|
||||||
|
|
||||||
if ! command -v setfacl &>/dev/null; then
|
|
||||||
warn "usr_ctl_dnshostfile: setfacl not found (install the 'acl' package) — skipping"
|
|
||||||
else
|
|
||||||
_dnshost_grp_exists=false
|
|
||||||
grep -qxF "usr_ctl_dnshostfile" <<< "$_ALL_USER_GROUPS" && _dnshost_grp_exists=true
|
|
||||||
|
|
||||||
_DNSHOST_USERS=()
|
|
||||||
if [[ "$_dnshost_grp_exists" == true ]]; then
|
|
||||||
while IFS= read -r _u; do
|
|
||||||
[[ -z "$_u" ]] && continue
|
|
||||||
_DNSHOST_USERS+=("$_u")
|
|
||||||
done < <(_ipa_group_member_users usr_ctl_dnshostfile)
|
|
||||||
fi
|
|
||||||
|
|
||||||
_dnshost_in_desired() {
|
|
||||||
local n="$1"
|
|
||||||
for _d in "${_DNSHOST_USERS[@]+"${_DNSHOST_USERS[@]}"}"; do [[ "$_d" == "$n" ]] && return 0; done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grant: add an ACL entry for every currently-desired user (idempotent).
|
|
||||||
for _u in "${_DNSHOST_USERS[@]+"${_DNSHOST_USERS[@]}"}"; do
|
|
||||||
getent passwd "$_u" &>/dev/null || continue
|
|
||||||
if ! getfacl "$HOSTS_FILE" 2>/dev/null | grep -qxF "user:${_u}:rw-"; then
|
|
||||||
setfacl -m "u:${_u}:rw" "$HOSTS_FILE" \
|
|
||||||
&& log "usr_ctl_dnshostfile: granted $_u write access to $HOSTS_FILE" \
|
|
||||||
|| warn "usr_ctl_dnshostfile: setfacl grant failed for $_u"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Revert: remove the ACL entry for anyone previously granted who is no
|
|
||||||
# longer in the group (or the group was deleted entirely).
|
|
||||||
_NEW_DNSHOST_STATE=()
|
|
||||||
while IFS= read -r _old_u; do
|
|
||||||
[[ -z "$_old_u" ]] && continue
|
|
||||||
if _dnshost_in_desired "$_old_u"; then
|
|
||||||
_NEW_DNSHOST_STATE+=("$_old_u")
|
|
||||||
else
|
|
||||||
setfacl -x "u:${_old_u}" "$HOSTS_FILE" 2>/dev/null \
|
|
||||||
&& log "usr_ctl_dnshostfile: revoked $_old_u write access to $HOSTS_FILE"
|
|
||||||
fi
|
|
||||||
done < "$DNSHOST_STATE"
|
|
||||||
|
|
||||||
if [[ ${#_DNSHOST_USERS[@]} -gt 0 ]]; then
|
|
||||||
printf '%s\n' "${_DNSHOST_USERS[@]}" | sort -u > "$DNSHOST_STATE"
|
|
||||||
else
|
|
||||||
> "$DNSHOST_STATE"
|
|
||||||
fi
|
|
||||||
unset _dnshost_grp_exists _DNSHOST_USERS _u _NEW_DNSHOST_STATE _old_u
|
|
||||||
fi
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# policy: usr_ctl_netman — grant specific IPA users the ability to modify this
|
|
||||||
# device's NON-WI-FI NetworkManager connections (IP address, gateway, DNS,
|
|
||||||
# routes, etc.) — NOT a blanket sudo/root grant, and deliberately NOT extended
|
|
||||||
# to Wi-Fi connections (see usr_ctl_wifi for that, kept separate on purpose).
|
|
||||||
#
|
|
||||||
# Mechanism: NetworkManager already lets a connection's designated non-root
|
|
||||||
# "permitted user" modify/activate THAT connection without a password
|
|
||||||
# (org.freedesktop.NetworkManager.settings.modify.own — allowed for local
|
|
||||||
# users by default on every mainstream distro's polkit rules). This policy
|
|
||||||
# adds the member to the `connection.permissions` list of every EXISTING
|
|
||||||
# non-Wi-Fi connection; Wi-Fi connections are left completely untouched, so
|
|
||||||
# a member can repoint the wired IP/gateway but cannot touch Wi-Fi settings.
|
|
||||||
#
|
|
||||||
# Requires: NetworkManager (nmcli) on the client.
|
|
||||||
# Note: applies to connections that exist at enforcement time; a brand-new
|
|
||||||
# wired connection created afterwards is picked up on the next 30-min tick.
|
|
||||||
# Users can already create/manage their OWN new connections by default
|
|
||||||
# (standard NetworkManager multi-user behavior) — this policy is specifically
|
|
||||||
# about granting access to EXISTING, previously root-owned connections.
|
|
||||||
|
|
||||||
NETMAN_STATE="$STATE_DIR/netman-acl-users"
|
|
||||||
[[ -f "$NETMAN_STATE" ]] || touch "$NETMAN_STATE"
|
|
||||||
|
|
||||||
if ! command -v nmcli &>/dev/null; then
|
|
||||||
warn "usr_ctl_netman: nmcli not found (NetworkManager not installed) — skipping"
|
|
||||||
else
|
|
||||||
_netman_grp_exists=false
|
|
||||||
grep -qxF "usr_ctl_netman" <<< "$_ALL_USER_GROUPS" && _netman_grp_exists=true
|
|
||||||
|
|
||||||
_NETMAN_USERS=()
|
|
||||||
if [[ "$_netman_grp_exists" == true ]]; then
|
|
||||||
while IFS= read -r _u; do
|
|
||||||
[[ -z "$_u" ]] && continue
|
|
||||||
_NETMAN_USERS+=("$_u")
|
|
||||||
done < <(_ipa_group_member_users usr_ctl_netman)
|
|
||||||
fi
|
|
||||||
|
|
||||||
_netman_in_desired() {
|
|
||||||
local n="$1"
|
|
||||||
for _d in "${_NETMAN_USERS[@]+"${_NETMAN_USERS[@]}"}"; do [[ "$_d" == "$n" ]] && return 0; done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Every current non-Wi-Fi connection UUID (ethernet, bridge, bond, vlan, ...).
|
|
||||||
_NON_WIFI_UUIDS=()
|
|
||||||
while IFS=: read -r _type _uuid; do
|
|
||||||
[[ -z "$_uuid" ]] && continue
|
|
||||||
[[ "$_type" == "wifi" || "$_type" == "802-11-wireless" ]] && continue
|
|
||||||
_NON_WIFI_UUIDS+=("$_uuid")
|
|
||||||
done < <(nmcli -t -f TYPE,UUID connection show 2>/dev/null)
|
|
||||||
|
|
||||||
# Grant: add each desired user to every non-Wi-Fi connection's permissions.
|
|
||||||
for _u in "${_NETMAN_USERS[@]+"${_NETMAN_USERS[@]}"}"; do
|
|
||||||
getent passwd "$_u" &>/dev/null || continue
|
|
||||||
for _uuid in "${_NON_WIFI_UUIDS[@]+"${_NON_WIFI_UUIDS[@]}"}"; do
|
|
||||||
_perms=$(nmcli -t -g connection.permissions connection show "$_uuid" 2>/dev/null)
|
|
||||||
if [[ "$_perms" != *"user:${_u}:"* ]]; then
|
|
||||||
nmcli connection modify "$_uuid" +connection.permissions "user:${_u}:" &>/dev/null \
|
|
||||||
&& log "usr_ctl_netman: granted $_u modify access to connection $_uuid"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# Revert: strip any previously-granted user from every non-Wi-Fi connection
|
|
||||||
# once they're no longer in the group (or the group is gone).
|
|
||||||
while IFS= read -r _old_u; do
|
|
||||||
[[ -z "$_old_u" ]] && continue
|
|
||||||
if ! _netman_in_desired "$_old_u"; then
|
|
||||||
for _uuid in "${_NON_WIFI_UUIDS[@]+"${_NON_WIFI_UUIDS[@]}"}"; do
|
|
||||||
_perms=$(nmcli -t -g connection.permissions connection show "$_uuid" 2>/dev/null)
|
|
||||||
[[ "$_perms" == *"user:${_old_u}:"* ]] && \
|
|
||||||
nmcli connection modify "$_uuid" -connection.permissions "user:${_old_u}:" &>/dev/null
|
|
||||||
done
|
|
||||||
log "usr_ctl_netman: revoked $_old_u network-modify access (left usr_ctl_netman)"
|
|
||||||
fi
|
|
||||||
done < "$NETMAN_STATE"
|
|
||||||
|
|
||||||
if [[ ${#_NETMAN_USERS[@]} -gt 0 ]]; then
|
|
||||||
printf '%s\n' "${_NETMAN_USERS[@]}" | sort -u > "$NETMAN_STATE"
|
|
||||||
else
|
|
||||||
> "$NETMAN_STATE"
|
|
||||||
fi
|
|
||||||
unset _netman_grp_exists _NETMAN_USERS _NON_WIFI_UUIDS _u _uuid _perms _old_u
|
|
||||||
fi
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# policy: usr_ctl_wifi — grant specific IPA users the ability to modify this
|
|
||||||
# device's Wi-Fi NetworkManager connections (SSID, PSK/security, etc.). Kept
|
|
||||||
# as its own group deliberately separate from usr_ctl_netman (wired IP/gateway
|
|
||||||
# control) — a user can be in either, both, or neither.
|
|
||||||
#
|
|
||||||
# Mechanism: identical to usr_ctl_netman but filtered to ONLY Wi-Fi
|
|
||||||
# connections — adds the member to `connection.permissions` on every existing
|
|
||||||
# Wi-Fi connection, relying on NetworkManager's own
|
|
||||||
# org.freedesktop.NetworkManager.settings.modify.own polkit rule (allowed for
|
|
||||||
# local users by default) to let them edit/activate those connections without
|
|
||||||
# a password. Wired/other connections are left completely untouched.
|
|
||||||
#
|
|
||||||
# Requires: NetworkManager (nmcli) on the client.
|
|
||||||
# Note: applies to connections that exist at enforcement time; a brand-new
|
|
||||||
# Wi-Fi connection created afterwards is picked up on the next 30-min tick.
|
|
||||||
|
|
||||||
WIFI_STATE="$STATE_DIR/wifi-acl-users"
|
|
||||||
[[ -f "$WIFI_STATE" ]] || touch "$WIFI_STATE"
|
|
||||||
|
|
||||||
if ! command -v nmcli &>/dev/null; then
|
|
||||||
warn "usr_ctl_wifi: nmcli not found (NetworkManager not installed) — skipping"
|
|
||||||
else
|
|
||||||
_wifi_grp_exists=false
|
|
||||||
grep -qxF "usr_ctl_wifi" <<< "$_ALL_USER_GROUPS" && _wifi_grp_exists=true
|
|
||||||
|
|
||||||
_WIFI_USERS=()
|
|
||||||
if [[ "$_wifi_grp_exists" == true ]]; then
|
|
||||||
while IFS= read -r _u; do
|
|
||||||
[[ -z "$_u" ]] && continue
|
|
||||||
_WIFI_USERS+=("$_u")
|
|
||||||
done < <(_ipa_group_member_users usr_ctl_wifi)
|
|
||||||
fi
|
|
||||||
|
|
||||||
_wifi_in_desired() {
|
|
||||||
local n="$1"
|
|
||||||
for _d in "${_WIFI_USERS[@]+"${_WIFI_USERS[@]}"}"; do [[ "$_d" == "$n" ]] && return 0; done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Every current Wi-Fi connection UUID.
|
|
||||||
_WIFI_UUIDS=()
|
|
||||||
while IFS=: read -r _type _uuid; do
|
|
||||||
[[ -z "$_uuid" ]] && continue
|
|
||||||
[[ "$_type" == "wifi" || "$_type" == "802-11-wireless" ]] || continue
|
|
||||||
_WIFI_UUIDS+=("$_uuid")
|
|
||||||
done < <(nmcli -t -f TYPE,UUID connection show 2>/dev/null)
|
|
||||||
|
|
||||||
# Grant: add each desired user to every Wi-Fi connection's permissions.
|
|
||||||
for _u in "${_WIFI_USERS[@]+"${_WIFI_USERS[@]}"}"; do
|
|
||||||
getent passwd "$_u" &>/dev/null || continue
|
|
||||||
for _uuid in "${_WIFI_UUIDS[@]+"${_WIFI_UUIDS[@]}"}"; do
|
|
||||||
_perms=$(nmcli -t -g connection.permissions connection show "$_uuid" 2>/dev/null)
|
|
||||||
if [[ "$_perms" != *"user:${_u}:"* ]]; then
|
|
||||||
nmcli connection modify "$_uuid" +connection.permissions "user:${_u}:" &>/dev/null \
|
|
||||||
&& log "usr_ctl_wifi: granted $_u modify access to Wi-Fi connection $_uuid"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# Revert: strip any previously-granted user from every Wi-Fi connection
|
|
||||||
# once they're no longer in the group (or the group is gone).
|
|
||||||
while IFS= read -r _old_u; do
|
|
||||||
[[ -z "$_old_u" ]] && continue
|
|
||||||
if ! _wifi_in_desired "$_old_u"; then
|
|
||||||
for _uuid in "${_WIFI_UUIDS[@]+"${_WIFI_UUIDS[@]}"}"; do
|
|
||||||
_perms=$(nmcli -t -g connection.permissions connection show "$_uuid" 2>/dev/null)
|
|
||||||
[[ "$_perms" == *"user:${_old_u}:"* ]] && \
|
|
||||||
nmcli connection modify "$_uuid" -connection.permissions "user:${_old_u}:" &>/dev/null
|
|
||||||
done
|
|
||||||
log "usr_ctl_wifi: revoked $_old_u Wi-Fi-modify access (left usr_ctl_wifi)"
|
|
||||||
fi
|
|
||||||
done < "$WIFI_STATE"
|
|
||||||
|
|
||||||
if [[ ${#_WIFI_USERS[@]} -gt 0 ]]; then
|
|
||||||
printf '%s\n' "${_WIFI_USERS[@]}" | sort -u > "$WIFI_STATE"
|
|
||||||
else
|
|
||||||
> "$WIFI_STATE"
|
|
||||||
fi
|
|
||||||
unset _wifi_grp_exists _WIFI_USERS _WIFI_UUIDS _u _uuid _perms _old_u
|
|
||||||
fi
|
|
||||||
|
|
@ -171,23 +171,10 @@ for _hgrp in dev_mon_clamscan dev_mon_timeshift dev_mon_power; do
|
||||||
ipa hostgroup-add "$_hgrp" &>/dev/null || true
|
ipa hostgroup-add "$_hgrp" &>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# usr_mon_logins / usr_mon_sshlogins / usr_mon_rightsviolations / usr_mon_iploc
|
# usr_mon_logins is a USER group (monitoring follows the user, not the device).
|
||||||
# are USER groups (monitoring follows the user, not the device). logins =
|
|
||||||
# failed LOCAL logins (console/tty/greeter); sshlogins = SSH activity;
|
|
||||||
# rightsviolations = sudo denials + SELinux/AppArmor access-control denials;
|
|
||||||
# iploc = public IP + geolocation, flags a country change — each tracked separately.
|
|
||||||
ipa group-show usr_mon_logins &>/dev/null 2>&1 || \
|
ipa group-show usr_mon_logins &>/dev/null 2>&1 || \
|
||||||
ipa group-add usr_mon_logins \
|
ipa group-add usr_mon_logins \
|
||||||
--desc="Members: failed local login monitoring enabled on enrolled devices" &>/dev/null || true
|
|
||||||
ipa group-show usr_mon_sshlogins &>/dev/null 2>&1 || \
|
|
||||||
ipa group-add usr_mon_sshlogins \
|
|
||||||
--desc="Members: SSH login monitoring enabled on enrolled devices" &>/dev/null || true
|
--desc="Members: SSH login monitoring enabled on enrolled devices" &>/dev/null || true
|
||||||
ipa group-show usr_mon_rightsviolations &>/dev/null 2>&1 || \
|
|
||||||
ipa group-add usr_mon_rightsviolations \
|
|
||||||
--desc="Members: sudo/SELinux/AppArmor rights-violation monitoring enabled on enrolled devices" &>/dev/null || true
|
|
||||||
ipa group-show usr_mon_iploc &>/dev/null 2>&1 || \
|
|
||||||
ipa group-add usr_mon_iploc \
|
|
||||||
--desc="Members: public IP/geolocation monitoring enabled on enrolled devices" &>/dev/null || true
|
|
||||||
|
|
||||||
kdestroy &>/dev/null || true
|
kdestroy &>/dev/null || true
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue