Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Kristian Klausen
Mail Credential Syncer
Commits
917f54b3
Verified
Commit
917f54b3
authored
Nov 16, 2020
by
Sven-Hendrik Haase
Browse files
Final foundation
parent
786dbe8a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Cargo.lock
View file @
917f54b3
...
...
@@ -384,17 +384,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bitflags"
version = "1.2.1"
...
...
@@ -410,17 +399,6 @@ dependencies = [
"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]]
name = "brotli-sys"
version = "0.3.2"
...
...
@@ -486,15 +464,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "clap"
version = "2.33.3"
...
...
@@ -817,17 +786,6 @@ dependencies = [
"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]]
name = "gimli"
version = "0.23.0"
...
...
@@ -1116,12 +1074,13 @@ dependencies = [
name = "mail-credential-syncer"
version = "0.1.0"
dependencies = [
"actix-rt",
"actix-web",
"anyhow",
"bcrypt",
"keycloak",
"log",
"pretty_env_logger",
"regex",
"reqwest",
"serde_json",
"structopt",
...
...
@@ -1476,7 +1435,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom
0.1.15
",
"getrandom",
"libc",
"rand_chacha",
"rand_core",
...
...
@@ -1499,7 +1458,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom
0.1.15
",
"getrandom",
]
[[package]]
...
...
Cargo.toml
View file @
917f54b3
...
...
@@ -16,11 +16,12 @@ codegen-units = 1
[dependencies]
structopt
=
"0.3"
keycloak
=
"11"
bcrypt
=
"
0.9
"
regex
=
"
1
"
url
=
"2"
reqwest
=
{
version
=
"0.10"
,
features
=
[
"json"
]
}
anyhow
=
"1"
actix-web
=
"3"
actix-rt
=
"1"
log
=
"0.4"
pretty_env_logger
=
"0.4"
serde_json
=
"1"
README.md
View file @
917f54b3
...
...
@@ -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:
git clone https://github.com/svenstaro/keycloak-http-webhook-provider.git
cd keycloak-http-webhook-provider
mvn clean install
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.
src/main.rs
View file @
917f54b3
use
actix_web
::{
middleware
::
Logger
,
post
,
web
,
App
,
HttpResponse
,
HttpServer
,
Responder
};
use
anyhow
::{
Context
,
Result
};
use
std
::
net
::
SocketAddr
;
use
log
::{
debug
,
trace
,
warn
};
use
regex
::
Regex
;
use
std
::
path
::
PathBuf
;
use
std
::{
net
::
SocketAddr
,
time
::
Duration
};
use
structopt
::
StructOpt
;
use
url
::
Url
;
...
...
@@ -16,51 +18,89 @@ struct CliConfig {
/// Local socket address to listen on
#[structopt(long)]
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
#[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
,
#[post(
"/webhook"
)]
async
fn
webhook
(
data
:
web
::
Json
<
serde_json
::
Value
>
)
->
impl
Responder
{
// 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
// validation errors in case the struct doesn't match the data sent. I think this gives us more
// flexibility in handling and ignoring most validation errors that we see while still ensuring
// we get valid JSON to start with.
/// Keycloak URL for incoming webhook validation and periodic syncs
#[structopt(long)]
keycloak_url
:
Url
,
trace!
(
"Received
\n
{:#}"
,
data
);
/// Keycloak admin username
#[structopt(long,
env)]
keycloak_admin_username
:
String
,
if
let
Some
(
hash
)
=
data
.pointer
(
"/representation/attributes/mail_password_hash/0"
)
{
debug!
(
"Received update with mail_password_hash: {:#}"
,
hash
);
/// Keycloak admin password
#[structopt(long,
env)]
keycloak_admin_password
:
String
,
}
if
let
Some
(
hash_value
)
=
hash
.as_str
()
{
let
re
=
Regex
::
new
(
r"^\$2y\$(.*)$"
)
.unwrap
();
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!"
)
}
#[actix_web::main]
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
();
// 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
))
.bind
(
"0.0.0.0:5000"
)
?
.bind
(
args
.listen
)
?
.run
()
.await
.context
(
"Failed to run actix"
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment