1 Commits

Author SHA1 Message Date
3f89eab11a WIP 2026-01-09 19:48:17 +09:00
22 changed files with 162 additions and 438 deletions

90
Cargo.lock generated
View File

@@ -292,9 +292,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.54"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
@@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.54"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@@ -325,9 +325,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.65"
version = "4.5.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
checksum = "004eef6b14ce34759aa7de4aea3217e368f463f46a3ed3764ca4b5a4404003b4"
dependencies = [
"clap",
"clap_lex",
@@ -353,6 +353,16 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clap_mangen"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "color-print"
version = "0.3.7"
@@ -695,7 +705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -1102,9 +1112,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
@@ -1127,7 +1137,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -1156,9 +1166,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
[[package]]
name = "jobserver"
@@ -1202,9 +1212,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.180"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libgit2-sys"
@@ -1337,6 +1347,7 @@ dependencies = [
"clap",
"clap-verbosity-flag",
"clap_complete",
"clap_mangen",
"color-print",
"const_format",
"derive_more",
@@ -1616,18 +1627,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.105"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.43"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
@@ -1769,6 +1780,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "roff"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "rsa"
version = "0.9.9"
@@ -1808,7 +1825,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -1914,9 +1931,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
dependencies = [
"indexmap",
"itoa",
@@ -1992,11 +2009,10 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
dependencies = [
"errno",
"libc",
]
@@ -2275,9 +2291,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@@ -2305,7 +2321,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -2395,9 +2411,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.49.0"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"bytes",
"libc",
@@ -2437,9 +2453,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.18"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -2448,9 +2464,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.18"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -2462,9 +2478,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
version = "0.9.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
dependencies = [
"indexmap",
"serde_core",
@@ -3222,9 +3238,9 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.10"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868"
checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d"
[[package]]
name = "zstd"

View File

@@ -22,9 +22,10 @@ autolib = false
anyhow = "1.0.100"
async-bincode = "0.8.0"
bincode = "2.0.1"
clap = { version = "4.5.54", features = ["cargo", "derive"] }
clap = { version = "4.5.53", features = ["cargo", "derive"] }
clap-verbosity-flag = { version = "3.0.4", features = [ "tracing" ] }
clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] }
clap_complete = { version = "4.5.62", features = ["unstable-dynamic"] }
clap_mangen = "0.2.31"
color-print = "0.3.7"
const_format = "0.2.35"
derive_more = { version = "2.1.1", features = ["display", "error"] }
@@ -38,14 +39,14 @@ num_cpus = "1.17.0"
prettytable = "0.10.0"
rand = "0.9.2"
serde = "1.0.228"
serde_json = { version = "1.0.149", features = ["preserve_order"] }
serde_json = { version = "1.0.148", features = ["preserve_order"] }
sqlx = { version = "0.8.6", features = ["runtime-tokio", "mysql", "tls-rustls"] }
thiserror = "2.0.17"
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "signal"] }
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "signal"] }
tokio-serde = { version = "0.9.0", features = ["bincode"] }
tokio-stream = "0.1.18"
tokio-util = { version = "0.7.18", features = ["codec", "rt"] }
toml = "0.9.11"
tokio-stream = "0.1.17"
tokio-util = { version = "0.7.17", features = ["codec", "rt"] }
toml = "0.9.10"
tracing = { version = "0.1.44", features = ["log"] }
tracing-subscriber = "0.3.22"
uuid = { version = "1.19.0", features = ["v4"] }

View File

@@ -47,8 +47,7 @@ over a IPC, which then performs the requested operations on behalf of the client
## Documentation
- [Installation and initial configuration](docs/installation.md)
- [Administration and further configuration](docs/administration.md)
- [Installation and configuration](docs/installation.md)
- [Development and testing](docs/development.md)
- [Compiling and packaging](docs/compiling.md)
- [Compatibility mode with mysql-admutils](docs/mysql-admutils-compatibility.md)

View File

@@ -1,5 +1,5 @@
# These are the default system groups on debian.
# You can also add groups by gid by prefixing the line with 'gid:'.
# You can alos add groups by gid by prefixing the line with 'gid:'.
group:_ssh
group:adm

View File

@@ -8,7 +8,7 @@ Type=notify
ExecStart=/usr/bin/muscl-server --systemd --disable-landlock socket-activate
ExecReload=/usr/bin/kill -HUP $MAINPID
WatchdogSec=3min
WatchdogSec=15
# Although this is a multi-instance unit, the constant `User` field is needed
# for authentication via mysql's auth_socket plugin to work.
@@ -61,7 +61,3 @@ SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
UMask=0777
[Install]
Also=muscl.socket
WantedBy=multi-user.target

View File

@@ -1,90 +0,0 @@
# Administration and further configuration
This page describes some additional configuration options and administration tasks for muscl.
## Configuring group denylists
In `/etc/muscl/muscl.conf`, you will find an option below `[authorization]` named `group_denylist_file`,
which points to `/etc/muscl/group_denylist.txt` by default.
In this file, you can add unix group names or GIDs to disallow the groups from being used as prefixes.
The deb package comes with a default denylist that disallows some common system groups.
The format of the file is one group name or GID per line. Lines starting with `#` and empty lines are ignored.
```
# Disallow using the 'root' group as a prefix
gid:0
# Disallow using the 'adm' group as a prefix
group:adm
```
> [!NOTE]
> If a user is named the same as a disallowed group, that user will still be able to use their username as a prefix.
## Configuring logging
By default, muscl logs to the systemd journal when run as a systemd service,
and also limits the log level to `info`. You can request more verbose logging
by appending `-v` flags to the `ExecStart=` line in the systemd service file.
To do this on a system where muscl was installed using a package, you can override
the service like this:
```bash
sudo systemctl edit muscl.service
```
This will open an editor where you can add the following lines:
```ini
[Service]
ExecStart=
ExecStart=/usr/bin/muscl-server -v ...
```
> [!NOTE]
> The first `ExecStart=` line is necessary to clear the previous value, as systemd
> interprets multiple `ExecStart=` lines as a list of commands to run in sequence.
You set either `-v` or `-vv` for `debug` and `trace` logging, respectively.
> [!WARNING]
> Be careful when enabling trace logging on production systems, as it might log
> passwords and credentials in plaintext.
## Querying logs in the systemd journal
Although invisible if you just run `journalctl -u muscl.service`, muscl adds a set of so-called
"fields" to its log entries to make it easier to filter and search them.
Here are some examples of how you can filter logs using `journalctl`:
```bash
# Show only logs related to a specific user
journalctl -eu muscl F_USER="<username>"
journalctl -eu muscl F_USER=johndoe
# Show only logs for a specific command types
journalctl -eu muscl F_COMMAND="<operation>"
journalctl -eu muscl F_COMMAND=create-db
# Show logs emitted for a specific session id
journalctl -eu muscl F_SESSION_ID="<session-id>"
journalctl -eu muscl F_SESSION_ID=123
# Show all of these fields together with the log message in a json format
journalctl --output json-pretty --output-fields MESSAGE,F_USER,F_COMMAND,F_SESSION_ID -eu muscl
```
See [`journalctl(1)`][journalctl_1] and [`systemd.journal-fields(7)`][systemd_journal-fields_7] for more information.
> [!NOTE]
> Please note that the commands are not 1-1 mapped to muscl subcommands.
> Rather, they are the available requests in the protocol used between the muscl client and server.
> These requests will often have the same name as the subcommands, but this is not always the case.
[journalctl_1]: https://man7.org/linux/man-pages/man1/journalctl.1.html
[systemd_journal-fields_7]: https://man7.org/linux/man-pages/man7/systemd.journal-fields.7.html

View File

@@ -39,12 +39,7 @@ docker stop mariadb
## Development using Nix
> [!NOTE]
> We have created some nix code to generate a QEMU VM with a setup similar to a production deployment
> There is not necessarily any VMs running in a production setup, and if so then at least not this VM.
> It is mainly there for easy access to interactive testing, as well as for testing the NixOS module.
If you have nix installed, you can easily test your changes in a NixOS test VM by running:
If you have nix installed, you can easily test your changes in a NixOS vm by running:
```bash
nix run .#vm # Start a NixOS VM in QEMU with muscl and MariaDB installed
@@ -52,3 +47,11 @@ nix run .#vm-mysql # Start a NixOS VM in QEMU with muscl and MySQL installed
```
You can configure the vm in `flake.nix`
## Filter logs by user with journalctl
If you want to filter the server logs by user, you can use journalctl's built-in filtering capabilities.
```bash
journalctl -eu muscl F_USER=<username>
```

View File

@@ -1,11 +1,9 @@
# Installation and initial configuration
# Installation and configuration
This document contains instructions for the recommended way of installing and configuring muscl.
Note that there are separate instructions for [installing on NixOS](nixos.md) and [installing with SUID/SGID mode](suid-sgid-mode.md).
After installation, you might want to look at the [Administration and further configuration](administration.md) page.
## Installing with deb on Debian
You can install muscl by adding the [PVV apt repository][pvv-apt-repository] and installing the package:
@@ -105,6 +103,28 @@ If you are using systemd, you should also create an override to unset the `Impor
ImportCredential=
```
## Configuring group denylists
In `/etc/muscl/muscl.conf`, you will find an option below `[authorization]` named `group_denylist_file`,
which points to `/etc/muscl/group_denylist.txt` by default.
In this file, you can add unix group names or GIDs to disallow the groups from being used as prefixes.
The deb package comes with a default denylist that disallows some common system groups.
The format of the file is one group name or GID per line. Lines starting with `#` and empty lines are ignored.
```
# Disallow using the 'root' group as a prefix
gid:0
# Disallow using the 'adm' group as a prefix
group:adm
```
> [!NOTE]
> If a user is named the same as a disallowed group, that user will still be able to use their username as a prefix.
## A note on minimum version requirements
The muscl server will work with older versions of systemd, but the recommended version is 254 or newer.

View File

@@ -4,8 +4,8 @@
> This will be deprecated in a future release, see https://git.pvv.ntnu.no/Projects/muscl/issues/101
>
> We do not recommend you use this mode unless you absolutely have to. The biggest reason why `muscl` was rewritten from scratch
> was to fix an architectural issue that easily caused vulnerabilities due to reliance on SUID/SGID. Although the architecture now
> is more resistant against such vulnerabilities, it is not failsafe.
> was to fix an architectural issue that easily caused vulnerabilites due to reliance on SUID/SGID. Althought the architecture now
> is more resistant against such vulnerabilites, it is not failsafe.
For backwards compatibility reasons, it is possible to run the program without a daemon by utilizing SUID/SGID.

View File

@@ -1,53 +0,0 @@
[Unit]
Description=Authorization daemon for Muscl
[Service]
Type=notify
ExecStart=/usr/local/bin/muscl_auth_daemon.py
# WatchdogSec=15
User=muscl
Group=muscl
DynamicUser=yes
; ConfigurationDirectory=muscl
; RuntimeDirectory=muscl
; # This is required to read unix user/group details.
; PrivateUsers=false
; # Needed to communicate with MySQL.
; PrivateNetwork=false
; PrivateIPC=false
; AmbientCapabilities=
; CapabilityBoundingSet=
; DeviceAllow=
; DevicePolicy=closed
; LockPersonality=true
; MemoryDenyWriteExecute=true
; NoNewPrivileges=true
; PrivateDevices=true
; PrivateMounts=true
; PrivateTmp=yes
; ProcSubset=pid
; ProtectClock=true
; ProtectControlGroups=strict
; ProtectHome=true
; ProtectHostname=true
; ProtectKernelLogs=true
; ProtectKernelModules=true
; ProtectKernelTunables=true
; ProtectProc=invisible
; ProtectSystem=strict
; RemoveIPC=true
; RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
; RestrictNamespaces=true
; RestrictRealtime=true
; RestrictSUIDSGID=true
; SocketBindDeny=any
; SystemCallArchitectures=native
; SystemCallFilter=@system-service
; SystemCallFilter=~@privileged @resources
; UMask=0777

View File

@@ -1,8 +0,0 @@
[Unit]
Description=Authorization daemon for Muscl
WantedBy=sockets.target
[Socket]
ListenStream=/run/muscl/muscl-auth-daemon.socket
Accept=no
SocketMode=0660

View File

@@ -1,84 +0,0 @@
#!/usr/bin/env python3
# TODO: create pool of workers to handle requests concurrently
# the socket should be a listener socket and each worker should accept connections from it
# the socket should accept requests as newline-separated JSON objects
# there should be a watchdog to monitor worker health and restart them if they die
# graceful shutdown should be implemented for the workers
# optional logging of requests and responses
# use systemd notify to signal readiness and amount of connections handled
import json
import os
from socket import AF_UNIX, SOCK_DGRAM, SOCK_STREAM, fromfd, socket
from multiprocessing import Pool
def get_listener_from_systemd() -> socket:
listen_fds = int(os.getenv("LISTEN_FDS", "0"))
listen_pid = int(os.getenv("LISTEN_PID", "0"))
if listen_fds != 1 or listen_pid != os.getpid():
raise RuntimeError("No socket passed from systemd")
assert listen_fds == 1
sock = fromfd(3, AF_UNIX, SOCK_STREAM)
sock.setblocking(False)
return sock
def get_notify_socket_from_systemd() -> socket:
notify_socket_path = os.getenv("NOTIFY_SOCKET")
if not notify_socket_path:
raise RuntimeError("No notify socket path found in environment")
sock = socket(AF_UNIX, SOCK_DGRAM)
sock.connect(notify_socket_path)
return sock
def run_auth_daemon(sock: socket):
sock.listen()
print("Auth daemon is running and listening for connections...")
with Pool() as worker_pool:
with get_notify_socket_from_systemd() as notify_socket:
notify_socket.sendall(b"READY=1\n")
while True:
conn, _ = sock.accept()
worker_pool.apply_async(session_handler, args=(conn,))
def session_handler(sock: socket):
buffer = ""
while True:
data = sock.recv(4096).decode("utf-8")
if not data:
print("Connection closed by client")
break
buffer += data
if buffer.endswith("\n"):
requests = buffer.strip().split("\n")
buffer = ""
for request in requests:
try:
req_json = json.loads(request)
username = req_json.get("username", "")
groups = req_json.get("groups", [])
resource_type = req_json.get("resource_type", "")
resource = req_json.get("resource", "")
allowed = process_request(username, groups, resource_type, resource)
response = {"allowed": allowed}
except json.JSONDecodeError:
response = {"error": "Invalid JSON"}
sock.sendall((json.dumps(response) + "\n").encode("utf-8"))
def process_request(
username: str,
groups: list[str],
resource_type: str,
resource: str,
) -> bool:
...
if __name__ == "__main__":
listener_socket = get_listener_from_systemd()
run_auth_daemon(listener_socket)

18
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1767744144,
"narHash": "sha256-9/9ntI0D+HbN4G0TrK3KmHbTvwgswz7p8IEJsWyef8Q=",
"lastModified": 1766774972,
"narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "2fb033290bf6b23f226d4c8b32f7f7a16b043d7e",
"rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82",
"type": "github"
},
"original": {
@@ -17,11 +17,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1768127708,
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
"lastModified": 1766902085,
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
"type": "github"
},
"original": {
@@ -45,11 +45,11 @@
]
},
"locked": {
"lastModified": 1768186348,
"narHash": "sha256-nkpIe3zkpeoFuOl8xBpexulECsHLQ9Ljg1gW3bPCjSI=",
"lastModified": 1766976750,
"narHash": "sha256-w+o3AIBI56tzfMJRqRXg9tSXnpQRN5hAT15o2t9rxYw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "af69e497567a5945a64057717bc9b17c8478097e",
"rev": "9fe44e7f05b734a64a01f92fc51ad064fb0a884f",
"type": "github"
},
"original": {

View File

@@ -105,7 +105,6 @@
fileset = lib.fileset.unions [
(craneLib.fileset.commonCargoSources ./.)
./assets
./examples
];
};
in {

View File

@@ -85,9 +85,6 @@ buildFunction ({
install -Dm644 assets/systemd/muscl.service -t "$out/lib/systemd/system"
substituteInPlace "$out/lib/systemd/system/muscl.service" \
--replace-fail '/usr/bin/muscl-server' "$out/bin/muscl-server"
mkdir -p "$out/share/muscl"
cp -r examples "$out/share/muscl"
'';
meta = with lib; {

View File

@@ -27,31 +27,6 @@ in
}.${level};
};
authHandler = lib.mkOption {
type = with lib.types; nullOr lines;
default = null;
description = "Custom authentication handler, written in python";
example = ''
def process_request(
username: str,
groups: list[str],
resource_type: str,
resource: str,
) -> bool:
if resource_type == "database":
if resource.startswith(username) or any(
resource.startswith(group) for group in groups
):
return True
elif resource_type == "user":
if resource.startswith(username) or any(
resource.startswith(group) for group in groups
):
return True
return False
'';
};
settings = lib.mkOption {
default = { };
type = lib.types.submodule {
@@ -180,14 +155,15 @@ in
systemd.services."muscl" = {
reloadTriggers = [ config.environment.etc."muscl/config.toml".source ];
serviceConfig = {
Type = "notify-reload";
ExecStart = [
""
"${lib.getExe' cfg.package "muscl-server"} ${cfg.logLevel} --systemd --disable-landlock socket-activate"
];
ExecReload = "";
ReloadSignal = "SIGHUP";
ExecReload = [
""
"${lib.getExe' pkgs.coreutils "kill"} -HUP $MAINPID"
];
RuntimeDirectory = "muscl/root-mnt";
RuntimeDirectoryMode = "0700";
@@ -216,72 +192,5 @@ in
++ (lib.optionals (cfg.settings.mysql.host != null) [ "AF_INET" "AF_INET6" ]);
};
};
systemd.sockets."muscl-auth-daemon" = lib.mkIf (cfg.authHandler != null) {
description = "Authorization daemon for Muscl";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "/run/muscl/muscl-auth-daemon.sock";
Accept = "no";
};
};
systemd.services."muscl-auth-daemon" = lib.mkIf (cfg.authHandler != null) {
description = "Authorization daemon for Muscl";
requires = [ "muscl-auth-daemon.socket" ];
serviceConfig = {
Type = "notify";
ExecStart = let
authScript = lib.pipe ../examples/auth_daemon_python/muscl_auth_daemon.py [
lib.fileContents
(lib.replaceString ''
def process_request(
username: str,
groups: list[str],
resource_type: str,
resource: str,
) -> bool:
...
'' cfg.authHandler)
(pkgs.writers.writePyPy3Bin "muscl-auth-handler.py" { })
];
in lib.getExe authScript;
User = "muscl-auth-daemon";
Group = "muscl-auth-daemon";
DynamicUser = true;
AmbientCapabilities = [ "" ];
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = "yes";
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = "strict";
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
UMask = "0777";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SocketBindDeny = [ "any" ];
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
};
};
};
}

View File

@@ -56,25 +56,6 @@ nixpkgs.lib.nixosSystem {
enable = true;
logLevel = "trace";
createLocalDatabaseUser = true;
authHandler = ''
def process_request(
username: str,
groups: list[str],
resource_type: str,
resource: str,
) -> bool:
if resource_type == "database":
if resource.startswith(username) or any(
resource.startswith(group) for group in groups
):
return True
elif resource_type == "user":
if resource.startswith(username) or any(
resource.startswith(group) for group in groups
):
return True
return False
'';
};
programs.vim = {

View File

@@ -305,6 +305,10 @@ fn main() -> anyhow::Result<()> {
return Ok(());
}
if handle_manpage_command()?.is_some() {
return Ok(());
}
#[cfg(feature = "mysql-admutils-compatibility")]
if handle_mysql_admutils_command()?.is_some() {
return Ok(());
@@ -361,6 +365,48 @@ fn handle_dynamic_completion() -> anyhow::Result<Option<()>> {
}
}
/// **WARNING:** This function may be run with elevated privileges.
fn handle_manpage_command() -> anyhow::Result<Option<()>> {
let argv1: Option<String> = std::env::args().nth(1);
match argv1.as_deref() {
Some("generate-manpages") => {
#[cfg(feature = "suid-sgid-mode")]
if executing_in_suid_sgid_mode()? {
use muscl_lib::core::bootstrap::drop_privs;
drop_privs()?
}
let output_dir = std::env::args().nth(2).ok_or(anyhow::anyhow!(
"Output directory argument missing for manpage generation"
))?;
let output_dir = PathBuf::from(&output_dir);
if !output_dir.is_dir() {
anyhow::bail!(
"Output directory `{:?}` does not exist or is not a directory",
output_dir,
);
}
let mut roff = clap_mangen::roff::Roff::new();
let man = clap_mangen::Man::new(Args::command());
man.render_title(&mut std::io::stdout())?;
man.render_name_section(&mut std::io::stdout())?;
man.render_synopsis_section(&mut std::io::stdout())?;
man.render_subcommands_section(&mut std::io::stdout())?;
man.render_options_section(&mut std::io::stdout())?;
roff.control("SH", ["VERSION"]);
roff.text([clap_mangen::roff::roman(AFTER_LONG_HELP)]);
roff.to_writer(&mut std::io::stdout())?;
Ok(Some(()))
}
_ => Ok(None),
}
}
/// **WARNING:** This function may be run with elevated privileges.
fn handle_mysql_admutils_command() -> anyhow::Result<Option<()>> {
let argv0 = std::env::args().next().and_then(|s| {
@@ -376,7 +422,7 @@ fn handle_mysql_admutils_command() -> anyhow::Result<Option<()>> {
}
}
/// Run the given command (from the client side) using Tokio.
/// Run the given commmand (from the client side) using Tokio.
fn tokio_run_command(
command: ClientCommand,
server_connection: StdUnixStream,

View File

@@ -35,7 +35,7 @@ spawn the editor stored in the $EDITOR environment variable.
(pico will be used if the variable is unset)
The file should contain one line per user, starting with the
username and followed by ten Y/N-values separated by whitespace.
username and followed by ten Y/N-values seperated by whitespace.
Lines starting with # are ignored.
The Y/N-values corresponds to the following mysql privileges:

View File

@@ -100,7 +100,7 @@ impl UnixUser {
})
}
// pub fn from_environment() -> anyhow::Result<Self> {
// pub fn from_enviroment() -> anyhow::Result<Self> {
// let libc_uid = nix::unistd::getuid();
// UnixUser::from_uid(libc_uid.as_raw())
// }

View File

@@ -140,7 +140,7 @@ impl Supervisor {
let (tx, rx) = broadcast::channel(1);
// TODO: try to detect systemd socket before using the provided socket path
// TODO: try to detech systemd socket before using the provided socket path
#[cfg(target_os = "linux")]
let listener = Arc::new(RwLock::new(match config.socket_path {
Some(ref path) => create_unix_listener_with_socket_path(path.clone()).await?,
@@ -295,15 +295,7 @@ impl Supervisor {
pub async fn reload(&self) -> anyhow::Result<()> {
#[cfg(target_os = "linux")]
sd_notify::notify(
false,
&[
sd_notify::NotifyState::Reloading,
sd_notify::NotifyState::monotonic_usec_now()
.expect("Failed to get monotonic time to send to systemd while reloading"),
sd_notify::NotifyState::Status("Reloading configuration"),
],
)?;
sd_notify::notify(false, &[sd_notify::NotifyState::Reloading])?;
let previous_config = self.config.lock().await.clone();
self.reload_config().await?;