Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3f89eab11a
|
789
Cargo.lock
generated
789
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
44
Cargo.toml
44
Cargo.toml
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "muscl"
|
||||
version = "1.0.0"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
resolver = "2"
|
||||
license = "BSD-3-Clause"
|
||||
authors = [
|
||||
"Programvareverkstedet <projects@pvv.ntnu.no>",
|
||||
"oysteikt@pvv.ntnu.no",
|
||||
"felixalb@pvv.ntnu.no",
|
||||
]
|
||||
homepage = "https://git.pvv.ntnu.no/Projects/muscl"
|
||||
repository = "https://git.pvv.ntnu.no/Projects/muscl"
|
||||
@@ -18,50 +19,51 @@ autobins = false
|
||||
autolib = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.100"
|
||||
async-bincode = "0.8.0"
|
||||
bincode = "2.0.1"
|
||||
clap = { version = "4.6.0", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.5.53", features = ["cargo", "derive"] }
|
||||
clap-verbosity-flag = { version = "3.0.4", features = [ "tracing" ] }
|
||||
clap_complete = { version = "4.6.0", 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"] }
|
||||
dialoguer = "0.12.0"
|
||||
futures-util = "0.3.32"
|
||||
futures-util = "0.3.31"
|
||||
humansize = "2.1.3"
|
||||
indoc = "2.0.7"
|
||||
itertools = "0.14.0"
|
||||
nix = { version = "0.31.2", features = ["fs", "process", "socket", "user"] }
|
||||
nix = { version = "0.30.1", features = ["fs", "process", "socket", "user"] }
|
||||
num_cpus = "1.17.0"
|
||||
prettytable = "0.10.0"
|
||||
rand = "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.18"
|
||||
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "signal"] }
|
||||
thiserror = "2.0.17"
|
||||
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 = "1.1.2"
|
||||
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.23"
|
||||
uuid = { version = "1.23.0", features = ["v4"] }
|
||||
tracing-subscriber = "0.3.22"
|
||||
uuid = { version = "1.19.0", features = ["v4"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
landlock = "0.4.4"
|
||||
sd-notify = "0.5.0"
|
||||
sd-notify = "0.4.5"
|
||||
tracing-journald = "0.3.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.102"
|
||||
build-info-build = "0.0.43"
|
||||
git2 = { version = "0.20.4", default-features = false }
|
||||
anyhow = "1.0.100"
|
||||
build-info-build = "0.0.42"
|
||||
git2 = { version = "0.20.3", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
regex = "1.12.3"
|
||||
regex = "1.12.2"
|
||||
|
||||
[features]
|
||||
default = ["mysql-admutils-compatibility"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,6 @@ Description=Muscl MySQL admin tool
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/muscl/muscl.sock
|
||||
RemoveOnStop=true
|
||||
Accept=no
|
||||
PassCredentials=true
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1774313767,
|
||||
"narHash": "sha256-hy0XTQND6avzGEUFrJtYBBpFa/POiiaGBr2vpU6Y9tY=",
|
||||
"lastModified": 1766774972,
|
||||
"narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "3d9df76e29656c679c744968b17fbaf28f0e923d",
|
||||
"rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,11 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1775036866,
|
||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
||||
"lastModified": 1766902085,
|
||||
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
||||
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -45,11 +45,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775099554,
|
||||
"narHash": "sha256-3xBsGnGDLOFtnPZ1D3j2LU19wpAlYefRKTlkv648rU0=",
|
||||
"lastModified": 1766976750,
|
||||
"narHash": "sha256-w+o3AIBI56tzfMJRqRXg9tSXnpQRN5hAT15o2t9rxYw=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "8d6387ed6d8e6e6672fd3ed4b61b59d44b124d99",
|
||||
"rev": "9fe44e7f05b734a64a01f92fc51ad064fb0a884f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -88,7 +88,7 @@ buildFunction ({
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
license = licenses.bsd3;
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux ++ platforms.darwin;
|
||||
inherit mainProgram;
|
||||
};
|
||||
|
||||
@@ -155,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";
|
||||
|
||||
@@ -54,13 +54,11 @@ for variant in debian-bookworm debian-trixie ubuntu-jammy ubuntu-noble; do
|
||||
DEB_VERSION=$(find "$TMPDIR/muscl-deb-$variant-$GIT_SHA"/*.deb -print0 | xargs -0 -n1 basename | cut -d'_' -f2 | head -n1)
|
||||
DEB_ARCH=$(find "$TMPDIR/muscl-deb-$variant-$GIT_SHA"/*.deb -print0 | xargs -0 -n1 basename | cut -d'_' -f3 | cut -d'.' -f1 | head -n1)
|
||||
|
||||
# echo "[DELETE] https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
# curl \
|
||||
# -X DELETE \
|
||||
# --user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
# "https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
curl \
|
||||
-X DELETE \
|
||||
--user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
"https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
|
||||
echo "[PUT] https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/upload"
|
||||
curl \
|
||||
-X PUT \
|
||||
--user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
|
||||
@@ -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(());
|
||||
@@ -319,8 +323,7 @@ fn main() -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "suid-sgid-mode"))]
|
||||
None,
|
||||
args.verbose,
|
||||
)
|
||||
.context("Failed to connect to the server")?;
|
||||
)?;
|
||||
|
||||
tokio_run_command(args.command, connection)?;
|
||||
|
||||
@@ -362,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| {
|
||||
@@ -377,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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
fs,
|
||||
os::unix::fs::FileTypeExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -8,10 +7,7 @@ use std::{
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
||||
use nix::{
|
||||
libc::{EXIT_SUCCESS, exit},
|
||||
unistd::{AccessFlags, access},
|
||||
};
|
||||
use nix::libc::{EXIT_SUCCESS, exit};
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use std::os::unix::net::UnixStream as StdUnixStream;
|
||||
use tokio::{net::UnixStream as TokioUnixStream, sync::RwLock};
|
||||
@@ -134,28 +130,11 @@ pub fn bootstrap_server_connection_and_drop_privileges(
|
||||
}
|
||||
}
|
||||
|
||||
fn socket_path_is_ok(path: &Path) -> anyhow::Result<()> {
|
||||
fs::metadata(path)
|
||||
.context(format!("Failed to get metadata for {:?}", path))
|
||||
.and_then(|meta| {
|
||||
if !meta.file_type().is_socket() {
|
||||
anyhow::bail!("{:?} is not a unix socket", path);
|
||||
}
|
||||
|
||||
access(path, AccessFlags::R_OK | AccessFlags::W_OK)
|
||||
.with_context(|| format!("Socket at {:?} is not readable/writable", path))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn connect_to_external_server(
|
||||
server_socket_path: Option<PathBuf>,
|
||||
) -> anyhow::Result<StdUnixStream> {
|
||||
// TODO: ensure this is both readable and writable
|
||||
if let Some(socket_path) = server_socket_path {
|
||||
tracing::trace!("Checking socket at {:?}", socket_path);
|
||||
socket_path_is_ok(&socket_path)?;
|
||||
|
||||
tracing::debug!("Connecting to socket at {:?}", socket_path);
|
||||
return match StdUnixStream::connect(socket_path) {
|
||||
Ok(socket) => Ok(socket),
|
||||
@@ -168,9 +147,6 @@ fn connect_to_external_server(
|
||||
}
|
||||
|
||||
if fs::metadata(DEFAULT_SOCKET_PATH).is_ok() {
|
||||
tracing::trace!("Checking socket at {:?}", DEFAULT_SOCKET_PATH);
|
||||
socket_path_is_ok(Path::new(DEFAULT_SOCKET_PATH))?;
|
||||
|
||||
tracing::debug!("Connecting to default socket at {:?}", DEFAULT_SOCKET_PATH);
|
||||
return match StdUnixStream::connect(DEFAULT_SOCKET_PATH) {
|
||||
Ok(socket) => Ok(socket),
|
||||
@@ -182,9 +158,7 @@ fn connect_to_external_server(
|
||||
};
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"No socket path provided, and no socket found found at default location {DEFAULT_SOCKET_PATH}"
|
||||
);
|
||||
anyhow::bail!("No socket path provided, and no default socket found");
|
||||
}
|
||||
|
||||
// TODO: this function is security critical, it should be integration tested
|
||||
|
||||
@@ -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())
|
||||
// }
|
||||
|
||||
@@ -34,7 +34,7 @@ impl DerefMut for MySQLUser {
|
||||
|
||||
impl fmt::Display for MySQLUser {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
write!(f, "{:<width$}", self.0, width = f.width().unwrap_or(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ impl DerefMut for MySQLDatabase {
|
||||
|
||||
impl fmt::Display for MySQLDatabase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
write!(f, "{:<width$}", self.0, width = f.width().unwrap_or(0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,6 @@ fn parse_group_denylist(denylist_path: &Path, lines: Lines) -> GroupDenylist {
|
||||
}
|
||||
.trim();
|
||||
|
||||
if trimmed_line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = trimmed_line.splitn(2, ':').collect();
|
||||
if parts.len() != 2 {
|
||||
tracing::warn!(
|
||||
|
||||
@@ -90,12 +90,14 @@ impl Supervisor {
|
||||
};
|
||||
|
||||
let mut watchdog_duration = None;
|
||||
let mut watchdog_micro_seconds = 0;
|
||||
#[cfg(target_os = "linux")]
|
||||
let watchdog_task =
|
||||
if systemd_mode && let Some(watchdog_duration_) = sd_notify::watchdog_enabled() {
|
||||
if systemd_mode && sd_notify::watchdog_enabled(true, &mut watchdog_micro_seconds) {
|
||||
let watchdog_duration_ = Duration::from_micros(watchdog_micro_seconds);
|
||||
tracing::debug!(
|
||||
"Systemd watchdog enabled with {} millisecond interval",
|
||||
watchdog_duration_.as_millis()
|
||||
watchdog_micro_seconds.div_ceil(1000),
|
||||
);
|
||||
watchdog_duration = Some(watchdog_duration_);
|
||||
Some(spawn_watchdog_task(watchdog_duration_))
|
||||
@@ -138,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?,
|
||||
@@ -293,12 +295,7 @@ impl Supervisor {
|
||||
|
||||
pub async fn reload(&self) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[
|
||||
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?;
|
||||
@@ -335,14 +332,14 @@ impl Supervisor {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Ready])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Stopping])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])?;
|
||||
|
||||
tracing::debug!("Stop accepting new connections");
|
||||
self.stop_receiving_new_connections()?;
|
||||
@@ -412,7 +409,7 @@ fn spawn_watchdog_task(duration: Duration) -> JoinHandle<()> {
|
||||
);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if let Err(err) = sd_notify::notify(&[sd_notify::NotifyState::Watchdog]) {
|
||||
if let Err(err) = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]) {
|
||||
tracing::warn!("Failed to notify systemd watchdog: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -435,7 +432,9 @@ fn spawn_status_notifier_task(task_tracker: TaskTracker) -> JoinHandle<()> {
|
||||
"Waiting for connections".to_string()
|
||||
};
|
||||
|
||||
if let Err(e) = sd_notify::notify(&[sd_notify::NotifyState::Status(message.as_str())]) {
|
||||
if let Err(e) =
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Status(message.as_str())])
|
||||
{
|
||||
tracing::warn!("Failed to send systemd status notification: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -550,7 +549,7 @@ async fn listener_task(
|
||||
group_denylist: Arc<RwLock<GroupDenylist>>,
|
||||
) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Ready])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
|
||||
|
||||
let connection_counter = AtomicU64::new(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user