Create OIDC group mappings via CLI

Adds new CLI subcommand `map-group` to create a mapping of OIDC group to
a role.
This commit is contained in:
Zoran Regvart
2026-08-31 17:57:55 +02:00
committed by Dave Kempe
parent cf917643d5
commit fcaae650d2
4 changed files with 132 additions and 0 deletions
Generated
+75
View File
@@ -179,6 +179,21 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "assert_cmd"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
dependencies = [
"anstyle",
"bstr",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "async-trait"
version = "0.1.91"
@@ -464,6 +479,17 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -945,6 +971,12 @@ dependencies = [
"cipher 0.5.2",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "digest"
version = "0.10.7"
@@ -2692,6 +2724,33 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "predicates"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
dependencies = [
"anstyle",
"difflib",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
[[package]]
name = "predicates-tree"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "primefield"
version = "0.14.0"
@@ -3256,6 +3315,7 @@ name = "rustguac"
version = "1.10.0"
dependencies = [
"aes",
"assert_cmd",
"axum",
"axum-server",
"base64 0.23.1",
@@ -3999,6 +4059,12 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "termtree"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -4545,6 +4611,15 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "want"
version = "0.3.1"
+3
View File
@@ -93,3 +93,6 @@ serde_urlencoded = "0.7"
[build-dependencies]
pulldown-cmark = "0.13"
[dev-dependencies]
assert_cmd = "2.2.2"
+53
View File
@@ -88,6 +88,14 @@ enum Command {
name: String,
},
/// Map an OIDC group to a role (admin, poweruser, operator, viewer)
MapGroup {
#[arg(long)]
group: String,
#[arg(long)]
role: String,
},
/// Rotate an admin's API key (generates new key, invalidates old)
RotateKey {
#[arg(long)]
@@ -217,6 +225,7 @@ async fn main() {
Some(Command::DisableAdmin { name }) => cmd_disable_admin(&database, &name),
Some(Command::EnableAdmin { name }) => cmd_enable_admin(&database, &name),
Some(Command::DeleteAdmin { name }) => cmd_delete_admin(&database, &name),
Some(Command::MapGroup { group, role }) => cmd_map_group(&database, &group, &role),
Some(Command::RotateKey { name }) => cmd_rotate_key(&database, &name),
Some(Command::GenerateCert {
hostname,
@@ -476,6 +485,20 @@ fn cmd_delete_user(database: &Db, email: &str) {
}
}
fn cmd_map_group(database: &Db, group: &str, role: &str) {
if !["admin", "poweruser", "operator", "viewer"].contains(&role) {
eprintln!("Role must be admin, poweruser, operator, or viewer.");
std::process::exit(1);
}
match db::create_group_mapping(database, group, role) {
Ok(_) => println!("Group '{}' mapped to role '{}'.", group, role),
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
}
/// Whether TLS is enabled (used by security headers middleware).
#[derive(Clone)]
struct TlsEnabled(bool);
@@ -1390,6 +1413,9 @@ fn rewrite_branding(html: &str, site_title: &str, logo_url: Option<&str>) -> Str
#[cfg(test)]
mod tests {
use assert_cmd::Command;
use std::path::Path;
use super::*;
#[test]
@@ -1472,4 +1498,31 @@ mod tests {
"no requests were throttled — rate-limit not applied"
);
}
#[test]
fn cli_map_group() {
if std::path::Path::new("tests/cli/rustguac.db").exists() {
std::fs::remove_file("tests/cli/rustguac.db")
.expect("Failed to delete old test configuration file");
}
let mut cmd = Command::cargo_bin("rustguac").unwrap();
cmd.current_dir("tests/cli")
.args(&[
"--config",
"config.toml",
"map-group",
"--group",
"superhumans",
"--role",
"admin",
])
.assert()
.success();
let database =
db::init_db(Path::new("tests/cli/rustguac.db")).expect("Failed to open database");
let mappings = db::list_group_mappings(&database).expect("Failed to list group mappings");
assert_eq!(mappings.len(), 1);
let mapping = &mappings[0];
assert_eq!(mapping.oidc_group, "superhumans");
assert_eq!(mapping.role, "admin");
}
}
+1
View File
@@ -0,0 +1 @@
db_path = "./rustguac.db"