From 9d7f226c8ed831ea7a6ed08d10329ccff7add693 Mon Sep 17 00:00:00 2001 From: jerhat Date: Thu, 9 Jul 2020 14:02:10 +0800 Subject: [PATCH 1/4] Added optional bitwarden_root_cert config to trust an additional root certificate when connecting to the bitwarden_rs instance. This can be useful in corporate environments where certificates are issued by a local CA (or for self-signed certificates) --- README.md | 1 + src/bw_admin.rs | 51 +++++++++++++++++++++++++++++++++++-------------- src/config.rs | 8 ++++++++ src/main.rs | 1 + 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ee54c08..ca58815 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Configuration values are as follows: |----|----|--------|-----------| |`bitwarden_url`|String||The root URL for accessing `bitwarden_rs`. Eg: `https://bw.example.com`| |`bitwarden_admin_token`|String||The value passed as `ADMIN_TOKEN` to `bitwarden_rs`| +|`bitwarden_root_cert`|String|Optional|Additional der-encoded root certificate to trust for accessing `bitwarden_rs`| |`ldap_host`|String||The hostname or IP address for your ldap server| |`ldap_scheme`|String|Optional|The that should be used to connect. `ldap` or `ldaps`. This is set by default based on SSL settings| |`ldap_ssl`|Boolean|Optional|Indicates if SSL should be used. Defaults to `false`| diff --git a/src/bw_admin.rs b/src/bw_admin.rs index e168a65..34ee24d 100644 --- a/src/bw_admin.rs +++ b/src/bw_admin.rs @@ -6,6 +6,8 @@ use serde::Deserialize; use std::collections::HashMap; use std::error::Error; use std::time::{Duration, Instant}; +use std::fs::File; +use std::io::Read; const COOKIE_LIFESPAN: Duration = Duration::from_secs(20 * 60); @@ -31,29 +33,50 @@ impl User { pub struct Client { url: String, admin_token: String, - cookie: Option, - cookie_created: Option, + root_cert: String, + cookie: Option, + cookie_created: Option } impl Client { /// Create new instance of client - pub fn new(url: String, admin_token: String) -> Client { + pub fn new(url: String, admin_token: String, root_cert: String) -> Client { Client { url, admin_token, - cookie: None, - cookie_created: None, + root_cert, + cookie: None, + cookie_created: None } } + fn get_http_client(&self) -> reqwest::Client { + + let mut client = reqwest::Client::builder() + .redirect(reqwest::RedirectPolicy::none()); + + if !&self.root_cert.is_empty() { + + let mut buf = Vec::new(); + + // read a local binary DER encoded certificate + File::open(&self.root_cert).expect("cannot open root cert").read_to_end(&mut buf).expect("cannot read root cert"); + + // create a certificate + let cert = reqwest::Certificate::from_der(&buf).expect("could not load der certificate"); + + // add the root cert + client = client.add_root_certificate(cert); + } + + return client.build().unwrap(); + } + /// Authenticate client fn auth(&mut self) -> Response { + let cookie_created = Instant::now(); - let client = reqwest::Client::builder() - // Avoid redirects because server will redirect to admin page after auth - .redirect(reqwest::RedirectPolicy::none()) - .build() - .unwrap(); + let client = &self.get_http_client(); let result = client .post(format!("{}{}", &self.url, "/admin/").as_str()) .form(&[("token", &self.admin_token)]) @@ -102,8 +125,8 @@ impl Client { } Some(cookie) => { let url = format!("{}/admin{}", &self.url, path); - let request = reqwest::Client::new() - .get(url.as_str()) + let client = self.get_http_client(); + let request = client.get(url.as_str()) .header(reqwest::header::COOKIE, cookie.clone()); let response = request.send().unwrap_or_else(|e| { panic!("Could not call with {}. {:?}", url, e); @@ -126,8 +149,8 @@ impl Client { } Some(cookie) => { let url = format!("{}/admin{}", &self.url, path); - let request = reqwest::Client::new() - .post(url.as_str()) + let client = self.get_http_client(); + let request = client.post(url.as_str()) .header("Cookie", cookie.clone()) .json(&json); let response = request.send().unwrap_or_else(|e| { diff --git a/src/config.rs b/src/config.rs index c63591c..958464a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -38,6 +38,7 @@ pub struct Config { // Bitwarden connection config bitwarden_url: String, bitwarden_admin_token: String, + bitwarden_root_cert: Option, // LDAP Connection config ldap_host: String, ldap_scheme: Option, @@ -71,6 +72,13 @@ impl Config { self.bitwarden_admin_token.clone() } + pub fn get_bitwarden_root_cert(&self) -> String { + match &self.bitwarden_root_cert { + Some(bitwarden_root_cert) => bitwarden_root_cert.clone(), + None => String::new(), + } + } + pub fn get_ldap_url(&self) -> String { format!( "{}://{}:{}", diff --git a/src/main.rs b/src/main.rs index 1be6919..3698012 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ fn main() { let mut client = bw_admin::Client::new( config.get_bitwarden_url().clone(), config.get_bitwarden_admin_token().clone(), + config.get_bitwarden_root_cert().clone() ); if let Err(e) = invite_users(&config, &mut client, config.get_ldap_sync_loop()) { From 78be95147420082e66fdf583d98e3f588edb32dd Mon Sep 17 00:00:00 2001 From: jerhat Date: Thu, 9 Jul 2020 14:24:36 +0800 Subject: [PATCH 2/4] Added optional ldap_no_tls_verify config that allows bypassiung ldap ssl certification check --- README.md | 1 + src/config.rs | 6 ++++++ src/main.rs | 10 ++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca58815..d3a5053 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Configuration values are as follows: |`ldap_host`|String||The hostname or IP address for your ldap server| |`ldap_scheme`|String|Optional|The that should be used to connect. `ldap` or `ldaps`. This is set by default based on SSL settings| |`ldap_ssl`|Boolean|Optional|Indicates if SSL should be used. Defaults to `false`| +|`ldap_no_tls_verify`|Boolean|Optional|Indicates if certificate should be verified when using SSL. Defaults to `true`| |`ldap_port`|Integer|Optional|Port used to connect to the LDAP server. This will default to 389 or 636, depending on your SSL settings| |`ldap_bind_dn`|String||The dn for the bind user that will connect to LDAP. Eg. `cn=admin,dc=example,dc=org`| |`ldap_bind_password`|String||The password for the provided bind user.| diff --git a/src/config.rs b/src/config.rs index 958464a..5c81c63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,8 @@ pub struct Config { ldap_scheme: Option, ldap_ssl: Option, ldap_port: Option, + // LDAP skip tls verify + ldap_no_tls_verify: Option, // LDAP auth config ldap_bind_dn: String, ldap_bind_password: Pass, @@ -109,6 +111,10 @@ impl Config { self.ldap_ssl.unwrap_or(false) } + pub fn get_ldap_no_tls_verify(&self) -> bool { + self.ldap_no_tls_verify.unwrap_or(false) + } + pub fn get_ldap_port(&self) -> u16 { match self.ldap_port { Some(ldap_port) => ldap_port, diff --git a/src/main.rs b/src/main.rs index 3698012..af10abd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::error::Error; use std::thread::sleep; use std::time::Duration; -use ldap3::{DerefAliases, LdapConn, Scope, SearchEntry, SearchOptions}; +use ldap3::{DerefAliases, LdapConn, Scope, SearchEntry, SearchOptions, LdapConnSettings}; mod bw_admin; mod config; @@ -65,8 +65,13 @@ fn ldap_client( ldap_url: String, bind_dn: String, bind_pw: String, + no_tls_verify: bool ) -> Result> { - let ldap = LdapConn::new(ldap_url.as_str())?; + + let settings = LdapConnSettings::new() + .set_no_tls_verify(no_tls_verify); + + let ldap = LdapConn::with_settings(settings, ldap_url.as_str())?; match ldap.simple_bind(bind_dn.as_str(), bind_pw.as_str()) { _ => {} }; @@ -80,6 +85,7 @@ fn search_entries(config: &config::Config) -> Result, Box Date: Thu, 9 Jul 2020 16:32:06 +0800 Subject: [PATCH 3/4] added instructions to expose the cert to container when using docker --- README.md | 2 +- docker-compose.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a5053..9373c2c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Configuration values are as follows: |----|----|--------|-----------| |`bitwarden_url`|String||The root URL for accessing `bitwarden_rs`. Eg: `https://bw.example.com`| |`bitwarden_admin_token`|String||The value passed as `ADMIN_TOKEN` to `bitwarden_rs`| -|`bitwarden_root_cert`|String|Optional|Additional der-encoded root certificate to trust for accessing `bitwarden_rs`| +|`bitwarden_root_cert`|String|Optional|Path to an additional der-encoded root certificate to trust. Eg. `root.cert`. If using Docker see `docker-compose.yml` for how to expose it. Defaults to `empty`| |`ldap_host`|String||The hostname or IP address for your ldap server| |`ldap_scheme`|String|Optional|The that should be used to connect. `ldap` or `ldaps`. This is set by default based on SSL settings| |`ldap_ssl`|Boolean|Optional|Indicates if SSL should be used. Defaults to `false`| diff --git a/docker-compose.yml b/docker-compose.yml index 365440c..37ae785 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: # dockerfile: Dockerfile.alpine volumes: - ./example.config.toml:/usr/src/bitwarden_rs_ldap/config.toml:ro + # ./root.cert:/usr/src/bitwarden_rs_ldap/root.cert:ro environment: RUST_BACKTRACE: 1 restart: always From f5b94ee79261522782231de9ea4255d42296a6b7 Mon Sep 17 00:00:00 2001 From: jerhat Date: Fri, 10 Jul 2020 11:14:12 +0800 Subject: [PATCH 4/4] renamed bitwarden_root_cert into bitwarden_root_cert_file separate get_root_cert() function formatting (rustfmt) --- README.md | 2 +- src/bw_admin.rs | 54 +++++++++++++++++++++++++------------------------ src/config.rs | 9 ++++----- src/main.rs | 13 +++++------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 9373c2c..54cd126 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Configuration values are as follows: |----|----|--------|-----------| |`bitwarden_url`|String||The root URL for accessing `bitwarden_rs`. Eg: `https://bw.example.com`| |`bitwarden_admin_token`|String||The value passed as `ADMIN_TOKEN` to `bitwarden_rs`| -|`bitwarden_root_cert`|String|Optional|Path to an additional der-encoded root certificate to trust. Eg. `root.cert`. If using Docker see `docker-compose.yml` for how to expose it. Defaults to `empty`| +|`bitwarden_root_cert_file`|String|Optional|Path to an additional der-encoded root certificate to trust. Eg. `root.cert`. If using Docker see `docker-compose.yml` for how to expose it. Defaults to `empty`| |`ldap_host`|String||The hostname or IP address for your ldap server| |`ldap_scheme`|String|Optional|The that should be used to connect. `ldap` or `ldaps`. This is set by default based on SSL settings| |`ldap_ssl`|Boolean|Optional|Indicates if SSL should be used. Defaults to `false`| diff --git a/src/bw_admin.rs b/src/bw_admin.rs index 34ee24d..03fda93 100644 --- a/src/bw_admin.rs +++ b/src/bw_admin.rs @@ -5,9 +5,9 @@ use reqwest::Response; use serde::Deserialize; use std::collections::HashMap; use std::error::Error; -use std::time::{Duration, Instant}; use std::fs::File; use std::io::Read; +use std::time::{Duration, Instant}; const COOKIE_LIFESPAN: Duration = Duration::from_secs(20 * 60); @@ -33,39 +33,40 @@ impl User { pub struct Client { url: String, admin_token: String, - root_cert: String, - cookie: Option, - cookie_created: Option + root_cert_file: String, + cookie: Option, + cookie_created: Option, } impl Client { /// Create new instance of client - pub fn new(url: String, admin_token: String, root_cert: String) -> Client { + pub fn new(url: String, admin_token: String, root_cert_file: String) -> Client { Client { url, admin_token, - root_cert, - cookie: None, - cookie_created: None + root_cert_file, + cookie: None, + cookie_created: None, } } + fn get_root_cert(&self) -> reqwest::Certificate { + let mut buf = Vec::new(); + + // read a local binary DER encoded certificate + File::open(&self.root_cert_file) + .expect("Could not open root cert file") + .read_to_end(&mut buf) + .expect("Could not read root cert file"); + + return reqwest::Certificate::from_der(&buf).expect("Could not load der root cert file"); + } + fn get_http_client(&self) -> reqwest::Client { + let mut client = reqwest::Client::builder().redirect(reqwest::RedirectPolicy::none()); - let mut client = reqwest::Client::builder() - .redirect(reqwest::RedirectPolicy::none()); - - if !&self.root_cert.is_empty() { - - let mut buf = Vec::new(); - - // read a local binary DER encoded certificate - File::open(&self.root_cert).expect("cannot open root cert").read_to_end(&mut buf).expect("cannot read root cert"); - - // create a certificate - let cert = reqwest::Certificate::from_der(&buf).expect("could not load der certificate"); - - // add the root cert + if !&self.root_cert_file.is_empty() { + let cert = self.get_root_cert(); client = client.add_root_certificate(cert); } @@ -74,9 +75,8 @@ impl Client { /// Authenticate client fn auth(&mut self) -> Response { - let cookie_created = Instant::now(); - let client = &self.get_http_client(); + let client = self.get_http_client(); let result = client .post(format!("{}{}", &self.url, "/admin/").as_str()) .form(&[("token", &self.admin_token)]) @@ -126,7 +126,8 @@ impl Client { Some(cookie) => { let url = format!("{}/admin{}", &self.url, path); let client = self.get_http_client(); - let request = client.get(url.as_str()) + let request = client + .get(url.as_str()) .header(reqwest::header::COOKIE, cookie.clone()); let response = request.send().unwrap_or_else(|e| { panic!("Could not call with {}. {:?}", url, e); @@ -150,7 +151,8 @@ impl Client { Some(cookie) => { let url = format!("{}/admin{}", &self.url, path); let client = self.get_http_client(); - let request = client.post(url.as_str()) + let request = client + .post(url.as_str()) .header("Cookie", cookie.clone()) .json(&json); let response = request.send().unwrap_or_else(|e| { diff --git a/src/config.rs b/src/config.rs index 5c81c63..eccc035 100644 --- a/src/config.rs +++ b/src/config.rs @@ -38,13 +38,12 @@ pub struct Config { // Bitwarden connection config bitwarden_url: String, bitwarden_admin_token: String, - bitwarden_root_cert: Option, + bitwarden_root_cert_file: Option, // LDAP Connection config ldap_host: String, ldap_scheme: Option, ldap_ssl: Option, ldap_port: Option, - // LDAP skip tls verify ldap_no_tls_verify: Option, // LDAP auth config ldap_bind_dn: String, @@ -74,9 +73,9 @@ impl Config { self.bitwarden_admin_token.clone() } - pub fn get_bitwarden_root_cert(&self) -> String { - match &self.bitwarden_root_cert { - Some(bitwarden_root_cert) => bitwarden_root_cert.clone(), + pub fn get_bitwarden_root_cert_file(&self) -> String { + match &self.bitwarden_root_cert_file { + Some(bitwarden_root_cert_file) => bitwarden_root_cert_file.clone(), None => String::new(), } } diff --git a/src/main.rs b/src/main.rs index af10abd..74d9866 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::error::Error; use std::thread::sleep; use std::time::Duration; -use ldap3::{DerefAliases, LdapConn, Scope, SearchEntry, SearchOptions, LdapConnSettings}; +use ldap3::{DerefAliases, LdapConn, LdapConnSettings, Scope, SearchEntry, SearchOptions}; mod bw_admin; mod config; @@ -15,7 +15,7 @@ fn main() { let mut client = bw_admin::Client::new( config.get_bitwarden_url().clone(), config.get_bitwarden_admin_token().clone(), - config.get_bitwarden_root_cert().clone() + config.get_bitwarden_root_cert_file().clone(), ); if let Err(e) = invite_users(&config, &mut client, config.get_ldap_sync_loop()) { @@ -65,12 +65,9 @@ fn ldap_client( ldap_url: String, bind_dn: String, bind_pw: String, - no_tls_verify: bool + no_tls_verify: bool, ) -> Result> { - - let settings = LdapConnSettings::new() - .set_no_tls_verify(no_tls_verify); - + let settings = LdapConnSettings::new().set_no_tls_verify(no_tls_verify); let ldap = LdapConn::with_settings(settings, ldap_url.as_str())?; match ldap.simple_bind(bind_dn.as_str(), bind_pw.as_str()) { _ => {} @@ -85,7 +82,7 @@ fn search_entries(config: &config::Config) -> Result, Box