Verified Commit 917f54b3 authored by Sven-Hendrik Haase's avatar Sven-Hendrik Haase
Browse files

Final foundation

parent 786dbe8a
...@@ -384,17 +384,6 @@ version = "0.13.0" ...@@ -384,17 +384,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bcrypt"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164"
dependencies = [
"base64 0.13.0",
"blowfish",
"getrandom 0.2.0",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
...@@ -410,17 +399,6 @@ dependencies = [ ...@@ -410,17 +399,6 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "blowfish"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b"
dependencies = [
"byteorder",
"cipher",
"opaque-debug",
]
[[package]] [[package]]
name = "brotli-sys" name = "brotli-sys"
version = "0.3.2" version = "0.3.2"
...@@ -486,15 +464,6 @@ version = "1.0.0" ...@@ -486,15 +464,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.33.3" version = "2.33.3"
...@@ -817,17 +786,6 @@ dependencies = [ ...@@ -817,17 +786,6 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "getrandom"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
dependencies = [
"cfg-if 0.1.10",
"libc",
"wasi",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.23.0" version = "0.23.0"
...@@ -1116,12 +1074,13 @@ dependencies = [ ...@@ -1116,12 +1074,13 @@ dependencies = [
name = "mail-credential-syncer" name = "mail-credential-syncer"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-rt",
"actix-web", "actix-web",
"anyhow", "anyhow",
"bcrypt",
"keycloak", "keycloak",
"log", "log",
"pretty_env_logger", "pretty_env_logger",
"regex",
"reqwest", "reqwest",
"serde_json", "serde_json",
"structopt", "structopt",
...@@ -1476,7 +1435,7 @@ version = "0.7.3" ...@@ -1476,7 +1435,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [ dependencies = [
"getrandom 0.1.15", "getrandom",
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
...@@ -1499,7 +1458,7 @@ version = "0.5.1" ...@@ -1499,7 +1458,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [ dependencies = [
"getrandom 0.1.15", "getrandom",
] ]
[[package]] [[package]]
......
...@@ -16,11 +16,12 @@ codegen-units = 1 ...@@ -16,11 +16,12 @@ codegen-units = 1
[dependencies] [dependencies]
structopt = "0.3" structopt = "0.3"
keycloak = "11" keycloak = "11"
bcrypt = "0.9" regex = "1"
url = "2" url = "2"
reqwest = { version = "0.10", features = [ "json" ] } reqwest = { version = "0.10", features = [ "json" ] }
anyhow = "1" anyhow = "1"
actix-web = "3" actix-web = "3"
actix-rt = "1"
log = "0.4" log = "0.4"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
serde_json = "1" serde_json = "1"
...@@ -6,4 +6,15 @@ Small tool to sync mail passwords from Keycloak to Dovecot ...@@ -6,4 +6,15 @@ Small tool to sync mail passwords from Keycloak to Dovecot
First of all, we need to start a pre-configured Keycloak server to run our tests against: First of all, we need to start a pre-configured Keycloak server to run our tests against:
git clone https://github.com/svenstaro/keycloak-http-webhook-provider.git
cd keycloak-http-webhook-provider
mvn clean install
keycloak/run-keycloak-container.sh keycloak/run-keycloak-container.sh
Once Keycloak is running, we can start our webhook server:
cargo run -- --listen 0.0.0.0:5000
Now go to your Keycloak server at http://localhost:8080, login via admin/admin and add a new user
attribute called `mail_password_hash` to a user of your choice. The webhook server should then give
you some output of what's happening.
use actix_web::{middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder}; use actix_web::{middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::net::SocketAddr; use log::{debug, trace, warn};
use regex::Regex;
use std::path::PathBuf; use std::path::PathBuf;
use std::{net::SocketAddr, time::Duration};
use structopt::StructOpt; use structopt::StructOpt;
use url::Url; use url::Url;
...@@ -16,51 +18,89 @@ struct CliConfig { ...@@ -16,51 +18,89 @@ struct CliConfig {
/// Local socket address to listen on /// Local socket address to listen on
#[structopt(long)] #[structopt(long)]
listen: SocketAddr, listen: SocketAddr,
// TODO Implement all of the below!
// /// Username for basic auth of HTTP endpoint
// #[structopt(long, env)]
// basic_auth_username: String,
//
// /// Password for basic auth of HTTP endpoint
// #[structopt(long, env)]
// basic_auth_password: String,
//
// /// Path to file containing Keycloak UUID to mail address mappings
// #[structopt(long)]
// mailmap: PathBuf,
//
// /// Path to a post-sync hook executable
// #[structopt(long)]
// post_sync_hook: PathBuf,
//
// /// Keycloak URL for incoming webhook validation and periodic syncs
// #[structopt(long)]
// keycloak_url: Url,
//
// /// Keycloak admin username
// #[structopt(long, env)]
// keycloak_admin_username: String,
//
// /// Keycloak admin password
// #[structopt(long, env)]
// keycloak_admin_password: String,
}
/// Username for basic auth of HTTP endpoint #[post("/webhook")]
#[structopt(long, env)] async fn webhook(data: web::Json<serde_json::Value>) -> impl Responder {
basic_auth_username: String, // We're getting a `serde_json::Value` here instead of trying to deserialize into a struct
// because we're only interested in a small subset of the data and we don't care about
/// Password for basic auth of HTTP endpoint // validation errors in case the struct doesn't match the data sent. I think this gives us more
#[structopt(long, env)] // flexibility in handling and ignoring most validation errors that we see while still ensuring
basic_auth_password: String, // we get valid JSON to start with.
/// Path to file containing Keycloak UUID to mail address mappings
#[structopt(long)]
mailmap: PathBuf,
/// Path to a post-sync hook executable
#[structopt(long)]
post_sync_hook: PathBuf,
/// Keycloak URL for incoming webhook validation and periodic syncs trace!("Received\n{:#}", data);
#[structopt(long)]
keycloak_url: Url,
/// Keycloak admin username if let Some(hash) = data.pointer("/representation/attributes/mail_password_hash/0") {
#[structopt(long, env)] debug!("Received update with mail_password_hash: {:#}", hash);
keycloak_admin_username: String,
/// Keycloak admin password if let Some(hash_value) = hash.as_str() {
#[structopt(long, env)] let re = Regex::new(r"^\$2y\$(.*)$").unwrap();
keycloak_admin_password: String, if let Some(hash_last_part) = re
} .captures(hash_value)
.and_then(|captured_value| captured_value.get(1))
{
let final_hash_value = hash_last_part.as_str();
// TODO Actually do something interesting here with the value.
} else {
warn!("Received hash but it has wrong format");
debug!("Hash in question is {:#}", hash_value);
return HttpResponse::BadRequest().body("Hash has wrong format");
}
} else {
warn!("Received update with mail_password_hash but ");
return HttpResponse::BadRequest().body("Empty hash value");
}
}
#[post("/webhook")]
async fn webhook(info: web::Json<serde_json::Value>) -> impl Responder {
println!("{:#}", info);
HttpResponse::Ok().body("Hello world!") HttpResponse::Ok().body("Hello world!")
} }
#[actix_web::main] #[actix_web::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "mail_credential_syncer=debug,actix_web=info");
pretty_env_logger::init(); pretty_env_logger::init();
// let args = CliConfig::from_args(); let args = CliConfig::from_args();
actix_rt::spawn(async move {
let mut interval = actix_rt::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
// TODO Do some periodic syncing somehow
debug!("syncing");
}
});
HttpServer::new(|| App::new().wrap(Logger::default()).service(webhook)) HttpServer::new(|| App::new().wrap(Logger::default()).service(webhook))
.bind("0.0.0.0:5000")? .bind(args.listen)?
.run() .run()
.await .await
.context("Failed to run actix") .context("Failed to run actix")
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment