add multi user setup

master
Dirk Heilig 2023-10-11 00:04:02 +02:00
parent 723f4d5a9d
commit 187494cd50
2 changed files with 70 additions and 17 deletions

View File

@ -9,12 +9,23 @@ This is a restic connector for kumar. It allows you to check if you backups did
Just run the container, ideally using compose and expose port 80, maybe use something like traffic to make it https. Just run the container, ideally using compose and expose port 80, maybe use something like traffic to make it https.
You could add a volume to /var/www/data to persist the data. You could add a volume to /var/www/data to persist the data.
This is not necessary, but a fresh container will not report functional backups until you reported something. This is not necessary, but a fresh container will not report functional backups until you reported something.
If it's nor prevented on a network level for the world to submit data, you might whant to set RKC_USER and RKC_PASS to prevent random people from submitting data. If it's not prevented on a network level for the world to submit data, you might want to use basic auth to prevent random people from submitting data.
You need to use these credentials when reporting to the webservice. You need to use these credentials when reporting to the webservice.
### authentication
For backwards compatibility you can have a single user / repo setup by setting the ENV-vars `RKC_USER` and `RKC_PASS`.
You should not use this, anymore on new setups.
Instead, you should use the new multi-user setup.
You have a single env-var for every user.
The name of the env-var is `RKC_USER_<username>`, the value is the password.
eg. to have 2 users, alice with the passwort secret and bob with the password hunter2, you would set the env-vars `RKC_USER_ALICE` and `RKC_USER_BOB` to `secret` and `hunter2`, respectively.
usernames only support alphanumeric characters and underscores and minus, they are case-insensitive.
There are no restrictions on passwords.
### Reporting ### Reporting
To report your you need to post the output of `restic snapshots` to the webservice, eg: To report your snapshots, you need to post the output of `restic snapshots` to the webservice, eg:
```bash ```bash
restic snapshots | curl -X POST -d @- http://restic_kumar_reporter/ restic snapshots | curl -X POST -d @- http://restic_kumar_reporter/
@ -31,6 +42,7 @@ when RKC_USER and RKC_PASS are set.
### Checking with kumar ### Checking with kumar
Just point kumar to your webservice. Just point kumar to your webservice.
You need to add an query parameter ?u= and the username used.
The Output looks something like this: The Output looks something like this:
``` ```

View File

@ -1,21 +1,55 @@
<?php <?php
function checkAuth($user, $password, $credentials, $mode)
{
if ($mode === "NO_AUTH") {
return true;
}
if (!isset($credentials[$user])) {
return false;
}
return $credentials[$user] === $password;
}
$credentials = [];
$mode = "NO_AUTH";
if (getenv("RKC_USER") !== false) {
$mode = "SINGLE_AUTH";
$credentials[getenv("RKC_USER")] = getenv("RKC_PASS");
}
foreach (getenv() as $k => $v) {
if (preg_match("/^RKC_USER_(.*)$/", $k, $m)) {
if ($mode === "SINGLE_AUTH") {
header("HTTP/1.0 500 Internal Server Error");
echo "single auth and multi auth is used at the same time";
exit();
}
$mode = "MULTI_AUTH";
$u = $m[1];
if (!preg_match("/^[a-zA-Z0-9_-]+$/", $k)) {
header("HTTP/1.0 500 Internal Server Error");
echo "invalid username, only a-zA-Z0-9 and _- are allowed";
exit();
}
$credentials[strtolower($u)] = $v;
}
}
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$user = getenv("RKC_USER");
$pass = getenv("RKC_PASS");
if (false !== $user or false !== $pass) {
if ( if (
!isset($_SERVER["PHP_AUTH_USER"]) || checkAuth(
!isset($_SERVER["PHP_AUTH_PW"]) || strtolower($_SERVER["PHP_AUTH_USER"] ?? null),
$_SERVER["PHP_AUTH_USER"] !== $user || $_SERVER["PHP_AUTH_PW"] ?? null,
$_SERVER["PHP_AUTH_PW"] !== $pass $credentials,
$mode
) === false
) { ) {
header('WWW-Authenticate: Basic realm="RKC"'); header('WWW-Authenticate: Basic realm="RKC"');
header("HTTP/1.0 401 Unauthorized"); header("HTTP/1.0 401 Unauthorized");
echo "You are not authorized to access this page."; echo "You are not authorized to access this page.";
exit(); exit();
} }
} $fileName = $_SERVER["PHP_AUTH_USER"] . ".json";
$resticData = file_get_contents("php://input"); $resticData = file_get_contents("php://input");
$resticData = explode("\n", $resticData); $resticData = explode("\n", $resticData);
@ -51,15 +85,22 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
} }
ksort($backups); ksort($backups);
file_put_contents( file_put_contents(
"/var/www/data/backups.json", "/var/www/data/$fileName",
json_encode($backups, JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT) json_encode($backups, JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT)
); );
exit(); exit();
} }
$fileName = $_GET["user"];
if (!isset($credentials[$fileName])) {
header("HTTP/1.0 404 Not Found");
echo "user not found";
exit();
}
$fileName .= ".json";
header("Content-Type: text/plain"); header("Content-Type: text/plain");
$maxAge = isset($_GET["maxage"]) ? intval($_GET["maxage"]) : 28; $maxAge = isset($_GET["maxage"]) ? intval($_GET["maxage"]) : 28;
$maxAge = $maxAge * 60 * 60; $maxAge = $maxAge * 60 * 60;
$backups = json_decode(file_get_contents("/var/www/data/backups.json"), true); $backups = json_decode(file_get_contents("/var/www/data/$fileName"), true);
echo "BACKUP|HOST|PATH|STATUS\n"; echo "BACKUP|HOST|PATH|STATUS\n";
foreach ($backups as $backupName => $backupTime) { foreach ($backups as $backupName => $backupTime) {
echo "BACKUP|$backupName|"; echo "BACKUP|$backupName|";