public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate
@ 2023-01-24 10:03 Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 1/6] ldap: create new `proxmox-ldap` crate Lukas Wagner
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

This patch series adds the new `proxmox-ldap` crate. The crate is mostly based on
`src/server/ldap.rs` from [1].

The main reason for breaking this out into a separate crate is to make it easily
reusable from PVE/PMG via perlmod -- at some point in the future, all
products could use the same LDAP implemenation.

This is sent as a separate patch series, as the original [1] was already
quite large with 17 commits, affecting multiple repositories.


Changes v2 -> v3:
  * Move the `proxmox-ldap` crate into the `proxmox` repo
  * Let FilterElement take &str instead of String
  * Implement the Display trait instead of ToString

Changes [1]@v1 -> v2:
  * Change how custom TLS-certificates work:
    Pass certificate paths instead of strings containing the
    certificate.
    Now, users of this crate can pass additional root certs that
    are to be trusted. Alternatively, and this was added with PVE
    compatibility in mind, one can add whole certificate store
    directories, replacing the system's default at `/etc/ssl/certs`.

  * Add integration tests, testing the implementation against a real
    LDAP server (`glauth`). The test can be executed via the
    `run_integratin_tests.sh` helper and require the `glauth` binary to
    be available. The integration tests are #[ignored] by default, so they
    don't interfere with regular unit-test execution.

Thanks to Wolfgang and Thomas for their review of v2.

[1] https://lists.proxmox.com/pipermail/pbs-devel/2023-January/005788.html

Lukas Wagner (6):
  ldap: create new `proxmox-ldap` crate
  ldap: add basic user auth functionality
  ldap: add helpers for constructing LDAP filters
  ldap: allow searching for LDAP entities
  ldap: tests: add LDAP integration tests
  ldap: add debian packaging

 Cargo.toml                                    |   3 +
 proxmox-ldap/Cargo.toml                       |  18 +
 proxmox-ldap/debian/changelog                 |   5 +
 proxmox-ldap/debian/control                   |  43 ++
 proxmox-ldap/debian/copyright                 |  16 +
 proxmox-ldap/debian/debcargo.toml             |   7 +
 proxmox-ldap/run_integration_tests.sh         |  31 ++
 proxmox-ldap/src/lib.rs                       | 390 ++++++++++++++++++
 .../tests/assets/generate_certificate.sh      |   4 +
 proxmox-ldap/tests/assets/glauth.cfg          |  67 +++
 proxmox-ldap/tests/assets/glauth.crt          |  29 ++
 proxmox-ldap/tests/assets/glauth.key          |  52 +++
 proxmox-ldap/tests/assets/glauth_v6.cfg       |  67 +++
 proxmox-ldap/tests/glauth.rs                  | 166 ++++++++
 14 files changed, 898 insertions(+)
 create mode 100644 proxmox-ldap/Cargo.toml
 create mode 100644 proxmox-ldap/debian/changelog
 create mode 100644 proxmox-ldap/debian/control
 create mode 100644 proxmox-ldap/debian/copyright
 create mode 100644 proxmox-ldap/debian/debcargo.toml
 create mode 100755 proxmox-ldap/run_integration_tests.sh
 create mode 100644 proxmox-ldap/src/lib.rs
 create mode 100755 proxmox-ldap/tests/assets/generate_certificate.sh
 create mode 100644 proxmox-ldap/tests/assets/glauth.cfg
 create mode 100644 proxmox-ldap/tests/assets/glauth.crt
 create mode 100644 proxmox-ldap/tests/assets/glauth.key
 create mode 100644 proxmox-ldap/tests/assets/glauth_v6.cfg
 create mode 100644 proxmox-ldap/tests/glauth.rs

-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 1/6] ldap: create new `proxmox-ldap` crate
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 2/6] ldap: add basic user auth functionality Lukas Wagner
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 Cargo.toml              |  1 +
 proxmox-ldap/Cargo.toml | 11 +++++++++++
 proxmox-ldap/src/lib.rs |  0
 3 files changed, 12 insertions(+)
 create mode 100644 proxmox-ldap/Cargo.toml
 create mode 100644 proxmox-ldap/src/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index 40570b6..91291f7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ members = [
     "proxmox-http",
     "proxmox-io",
     "proxmox-lang",
+    "proxmox-ldap",
     "proxmox-metrics",
     "proxmox-rest-server",
     "proxmox-router",
diff --git a/proxmox-ldap/Cargo.toml b/proxmox-ldap/Cargo.toml
new file mode 100644
index 0000000..00358fc
--- /dev/null
+++ b/proxmox-ldap/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "proxmox-ldap"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+exclude.workspace = true
+description = "Proxmox library for LDAP authentication/synchronization"
+
+[dependencies]
diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs
new file mode 100644
index 0000000..e69de29
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 2/6] ldap: add basic user auth functionality
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 1/6] ldap: create new `proxmox-ldap` crate Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 3/6] ldap: add helpers for constructing LDAP filters Lukas Wagner
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

In the LDAP world, authentication is done using the bind operation, where
users are authenticated with the tuple (dn, password). Since we only know
the user's username, it is first necessary to look up the user's
domain (dn).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 Cargo.toml              |   2 +
 proxmox-ldap/Cargo.toml |   5 +
 proxmox-ldap/src/lib.rs | 228 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 235 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index 91291f7..5489153 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,8 +49,10 @@ hex = "0.4"
 http = "0.2"
 hyper = "0.14.5"
 lazy_static = "1.4"
+ldap3 = "0.11"
 libc = "0.2.107"
 log = "0.4.17"
+native-tls = "0.2"
 nix = "0.26.1"
 once_cell = "1.3.1"
 openssl = "0.10"
diff --git a/proxmox-ldap/Cargo.toml b/proxmox-ldap/Cargo.toml
index 00358fc..70fba73 100644
--- a/proxmox-ldap/Cargo.toml
+++ b/proxmox-ldap/Cargo.toml
@@ -9,3 +9,8 @@ exclude.workspace = true
 description = "Proxmox library for LDAP authentication/synchronization"
 
 [dependencies]
+anyhow.workspace = true
+ldap3 = { workspace = true, default_features = false, features = ["tls"] }
+serde = { workspace = true, features = ["derive"] }
+native-tls.workspace = true
+
diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs
index e69de29..48eb863 100644
--- a/proxmox-ldap/src/lib.rs
+++ b/proxmox-ldap/src/lib.rs
@@ -0,0 +1,228 @@
+use std::{
+    fs,
+    path::{Path, PathBuf},
+    time::Duration,
+};
+
+use anyhow::{bail, Error};
+use ldap3::{
+    Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry,
+};
+use native_tls::{Certificate, TlsConnector, TlsConnectorBuilder};
+use serde::{Deserialize, Serialize};
+
+#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
+/// LDAP connection security
+pub enum LdapConnectionMode {
+    /// unencrypted connection
+    Ldap,
+    /// upgrade to TLS via STARTTLS
+    StartTls,
+    /// TLS via LDAPS
+    Ldaps,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+/// Configuration for LDAP connections
+pub struct LdapConfig {
+    /// Array of servers that will be tried in order
+    pub servers: Vec<String>,
+    /// Port
+    pub port: Option<u16>,
+    /// LDAP attribute containing the user id. Will be used to look up the user's domain
+    pub user_attr: String,
+    /// LDAP base domain
+    pub base_dn: String,
+    /// LDAP bind domain, will be used for user lookup/sync if set
+    pub bind_dn: Option<String>,
+    /// LDAP bind password, will be used for user lookup/sync if set
+    pub bind_password: Option<String>,
+    /// Connection security
+    pub tls_mode: LdapConnectionMode,
+    /// Verify the server's TLS certificate
+    pub verify_certificate: bool,
+    /// Root certificates that should be trusted, in addition to
+    /// the ones from the certificate store.
+    /// Expects X.509 certs in PEM format.
+    pub additional_trusted_certificates: Option<Vec<PathBuf>>,
+    /// Override the path to the system's default certificate store
+    /// in /etc/ssl/certs (added for PVE compatibility)
+    pub certificate_store_path: Option<PathBuf>,
+}
+
+/// Connection to an LDAP server, can be used to authenticate users.
+pub struct LdapConnection {
+    /// Configuration for this connection
+    config: LdapConfig,
+}
+
+impl LdapConnection {
+    /// Default port for LDAP/StartTls connections
+    const LDAP_DEFAULT_PORT: u16 = 389;
+    /// Default port for LDAPS connections
+    const LDAPS_DEFAULT_PORT: u16 = 636;
+    /// Connection timeout
+    const LDAP_CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
+
+    /// Create a new LDAP connection.
+    pub fn new(config: LdapConfig) -> Self {
+        Self { config }
+    }
+
+    /// Authenticate a user with username/password.
+    ///
+    /// The user's domain is queried is by performing an LDAP search with the configured bind_dn
+    /// and bind_password. If no bind_dn is provided, an anonymous search is attempted.
+    pub async fn authenticate_user(&self, username: &str, password: &str) -> Result<(), Error> {
+        let user_dn = self.search_user_dn(username).await?;
+
+        let mut ldap = self.create_connection().await?;
+
+        // Perform actual user authentication by binding.
+        let _: LdapResult = ldap.simple_bind(&user_dn, password).await?.success()?;
+
+        // We are already authenticated, so don't fail if terminating the connection
+        // does not work for some reason.
+        let _: Result<(), _> = ldap.unbind().await;
+
+        Ok(())
+    }
+
+    /// Retrive port from LDAP configuration, otherwise use the correct default
+    fn port_from_config(&self) -> u16 {
+        self.config.port.unwrap_or_else(|| {
+            if self.config.tls_mode == LdapConnectionMode::Ldaps {
+                Self::LDAPS_DEFAULT_PORT
+            } else {
+                Self::LDAP_DEFAULT_PORT
+            }
+        })
+    }
+
+    /// Determine correct URL scheme from LDAP config
+    fn scheme_from_config(&self) -> &'static str {
+        if self.config.tls_mode == LdapConnectionMode::Ldaps {
+            "ldaps"
+        } else {
+            "ldap"
+        }
+    }
+
+    /// Construct URL from LDAP config
+    fn ldap_url_from_config(&self, server: &str) -> String {
+        let port = self.port_from_config();
+        let scheme = self.scheme_from_config();
+        format!("{scheme}://{server}:{port}")
+    }
+
+    fn add_cert_to_builder<P: AsRef<Path>>(
+        path: P,
+        builder: &mut TlsConnectorBuilder,
+    ) -> Result<(), Error> {
+        let bytes = fs::read(path)?;
+        let cert = Certificate::from_pem(&bytes)?;
+        builder.add_root_certificate(cert);
+
+        Ok(())
+    }
+
+    async fn try_connect(&self, url: &str) -> Result<(LdapConnAsync, Ldap), Error> {
+        let starttls = self.config.tls_mode == LdapConnectionMode::StartTls;
+
+        let mut builder = TlsConnector::builder();
+        builder.danger_accept_invalid_certs(!self.config.verify_certificate);
+
+        if let Some(certificate_paths) = self.config.additional_trusted_certificates.as_deref() {
+            for path in certificate_paths {
+                Self::add_cert_to_builder(path, &mut builder)?;
+            }
+        }
+
+        if let Some(certificate_store_path) = self.config.certificate_store_path.as_deref() {
+            builder.disable_built_in_roots(true);
+
+            for dir_entry in fs::read_dir(certificate_store_path)? {
+                let dir_entry = dir_entry?;
+
+                if !dir_entry.metadata()?.is_dir() {
+                    Self::add_cert_to_builder(dir_entry.path(), &mut builder)?;
+                }
+            }
+        }
+
+        LdapConnAsync::with_settings(
+            LdapConnSettings::new()
+                .set_starttls(starttls)
+                .set_conn_timeout(Self::LDAP_CONNECTION_TIMEOUT)
+                .set_connector(builder.build()?),
+            url,
+        )
+        .await
+        .map_err(|e| e.into())
+    }
+
+    /// Create LDAP connection
+    ///
+    /// If a connection to the server cannot be established, the fallbacks
+    /// are tried.
+    async fn create_connection(&self) -> Result<Ldap, Error> {
+        let mut last_error = None;
+
+        for server in &self.config.servers {
+            match self.try_connect(&self.ldap_url_from_config(server)).await {
+                Ok((connection, ldap)) => {
+                    ldap3::drive!(connection);
+                    return Ok(ldap);
+                }
+                Err(e) => {
+                    last_error = Some(e);
+                }
+            }
+        }
+
+        Err(last_error.unwrap())
+    }
+
+    /// Search a user's domain.
+    async fn search_user_dn(&self, username: &str) -> Result<String, Error> {
+        let mut ldap = self.create_connection().await?;
+
+        if let Some(bind_dn) = self.config.bind_dn.as_deref() {
+            let password = self.config.bind_password.as_deref().unwrap_or_default();
+            let _: LdapResult = ldap.simple_bind(bind_dn, password).await?.success()?;
+
+            let user_dn = self.do_search_user_dn(username, &mut ldap).await;
+
+            ldap.unbind().await?;
+
+            user_dn
+        } else {
+            self.do_search_user_dn(username, &mut ldap).await
+        }
+    }
+
+    async fn do_search_user_dn(&self, username: &str, ldap: &mut Ldap) -> Result<String, Error> {
+        let query = format!("(&({}={}))", self.config.user_attr, username);
+
+        let (entries, _res) = ldap
+            .search(&self.config.base_dn, Scope::Subtree, &query, vec!["dn"])
+            .await?
+            .success()?;
+
+        if entries.len() > 1 {
+            bail!(
+                "found multiple users with attribute `{}={}`",
+                self.config.user_attr,
+                username
+            )
+        }
+
+        if let Some(entry) = entries.into_iter().next() {
+            let entry = SearchEntry::construct(entry);
+
+            return Ok(entry.dn);
+        }
+
+        bail!("user not found")
+    }
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 3/6] ldap: add helpers for constructing LDAP filters
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 1/6] ldap: create new `proxmox-ldap` crate Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 2/6] ldap: add basic user auth functionality Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 4/6] ldap: allow searching for LDAP entities Lukas Wagner
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 proxmox-ldap/src/lib.rs | 79 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 76 insertions(+), 3 deletions(-)

diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs
index 48eb863..ac164db 100644
--- a/proxmox-ldap/src/lib.rs
+++ b/proxmox-ldap/src/lib.rs
@@ -1,13 +1,12 @@
 use std::{
+    fmt::{Display, Formatter},
     fs,
     path::{Path, PathBuf},
     time::Duration,
 };
 
 use anyhow::{bail, Error};
-use ldap3::{
-    Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry,
-};
+use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry};
 use native_tls::{Certificate, TlsConnector, TlsConnectorBuilder};
 use serde::{Deserialize, Serialize};
 
@@ -226,3 +225,77 @@ impl LdapConnection {
         bail!("user not found")
     }
 }
+
+#[allow(dead_code)]
+enum FilterElement<'a> {
+    And(Vec<FilterElement<'a>>),
+    Or(Vec<FilterElement<'a>>),
+    Condition(&'a str, &'a str),
+    Not(Box<FilterElement<'a>>),
+    Verbatim(&'a str),
+}
+
+impl<'a> Display for FilterElement<'a> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        fn write_children(f: &mut Formatter<'_>, children: &[FilterElement]) -> std::fmt::Result {
+            for child in children {
+                write!(f, "{child}")?;
+            }
+
+            Ok(())
+        }
+
+        match self {
+            FilterElement::And(children) => {
+                write!(f, "(&")?;
+                write_children(f, children)?;
+                write!(f, ")")?;
+            }
+            FilterElement::Or(children) => {
+                write!(f, "(|")?;
+                write_children(f, children)?;
+                write!(f, ")")?;
+            }
+            FilterElement::Not(element) => {
+                write!(f, "(!{})", element)?;
+            }
+            FilterElement::Condition(attr, value) => {
+                write!(f, "({attr}={value})")?;
+            }
+            FilterElement::Verbatim(verbatim) => write!(f, "{verbatim}")?,
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::FilterElement::*;
+
+    #[test]
+    fn test_filter_elements_to_string() {
+        assert_eq!("(uid=john)", Condition("uid", "john").to_string());
+        assert_eq!(
+            "(!(uid=john))",
+            Not(Box::new(Condition("uid", "john"))).to_string()
+        );
+
+        assert_eq!("(foo=bar)", &Verbatim("(foo=bar)").to_string());
+
+        let filter_string = And(vec![
+            Condition("givenname", "john"),
+            Condition("sn", "doe"),
+            Or(vec![
+                Condition("email", "john@foo"),
+                Condition("email", "john@bar"),
+            ]),
+        ])
+        .to_string();
+
+        assert_eq!(
+            "(&(givenname=john)(sn=doe)(|(email=john@foo)(email=john@bar)))",
+            &filter_string
+        );
+    }
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 4/6] ldap: allow searching for LDAP entities
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
                   ` (2 preceding siblings ...)
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 3/6] ldap: add helpers for constructing LDAP filters Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 5/6] ldap: tests: add LDAP integration tests Lukas Wagner
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

This commit adds the search_entities function, which allows to search for
LDAP entities given certain provided criteria.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 proxmox-ldap/src/lib.rs | 89 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)

diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs
index ac164db..903ce1a 100644
--- a/proxmox-ldap/src/lib.rs
+++ b/proxmox-ldap/src/lib.rs
@@ -1,4 +1,5 @@
 use std::{
+    collections::HashMap,
     fmt::{Display, Formatter},
     fs,
     path::{Path, PathBuf},
@@ -6,6 +7,7 @@ use std::{
 };
 
 use anyhow::{bail, Error};
+use ldap3::adapters::{Adapter, EntriesOnly, PagedResults};
 use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry};
 use native_tls::{Certificate, TlsConnector, TlsConnectorBuilder};
 use serde::{Deserialize, Serialize};
@@ -49,6 +51,26 @@ pub struct LdapConfig {
     pub certificate_store_path: Option<PathBuf>,
 }
 
+#[derive(Serialize, Deserialize)]
+/// Parameters for LDAP user searches
+pub struct SearchParameters {
+    /// Attributes that should be retrieved
+    pub attributes: Vec<String>,
+    /// `objectclass`es of intereset
+    pub user_classes: Vec<String>,
+    /// Custom user filter
+    pub user_filter: Option<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+/// Single LDAP user search result
+pub struct SearchResult {
+    /// The full user's domain
+    pub dn: String,
+    /// Queried user attributes
+    pub attributes: HashMap<String, Vec<String>>,
+}
+
 /// Connection to an LDAP server, can be used to authenticate users.
 pub struct LdapConnection {
     /// Configuration for this connection
@@ -87,6 +109,51 @@ impl LdapConnection {
         Ok(())
     }
 
+    /// Query entities matching given search parameters
+    pub async fn search_entities(
+        &self,
+        parameters: &SearchParameters,
+    ) -> Result<Vec<SearchResult>, Error> {
+        let search_filter = Self::assemble_search_filter(parameters);
+
+        let mut ldap = self.create_connection().await?;
+
+        if let Some(bind_dn) = self.config.bind_dn.as_deref() {
+            let password = self.config.bind_password.as_deref().unwrap_or_default();
+            let _: LdapResult = ldap.simple_bind(bind_dn, password).await?.success()?;
+        }
+
+        let adapters: Vec<Box<dyn Adapter<_, _>>> = vec![
+            Box::new(EntriesOnly::new()),
+            Box::new(PagedResults::new(500)),
+        ];
+        let mut search = ldap
+            .streaming_search_with(
+                adapters,
+                &self.config.base_dn,
+                Scope::Subtree,
+                &search_filter,
+                parameters.attributes.clone(),
+            )
+            .await?;
+
+        let mut results = Vec::new();
+
+        while let Some(entry) = search.next().await? {
+            let entry = SearchEntry::construct(entry);
+
+            results.push(SearchResult {
+                dn: entry.dn,
+                attributes: entry.attrs,
+            })
+        }
+        let _res = search.finish().await.success()?;
+
+        let _ = ldap.unbind().await;
+
+        Ok(results)
+    }
+
     /// Retrive port from LDAP configuration, otherwise use the correct default
     fn port_from_config(&self) -> u16 {
         self.config.port.unwrap_or_else(|| {
@@ -224,6 +291,28 @@ impl LdapConnection {
 
         bail!("user not found")
     }
+
+    fn assemble_search_filter(parameters: &SearchParameters) -> String {
+        use FilterElement::*;
+
+        let attr_wildcards = Or(parameters
+            .attributes
+            .iter()
+            .map(|attr| Condition(attr, "*"))
+            .collect());
+        let user_classes = Or(parameters
+            .user_classes
+            .iter()
+            .map(|class| Condition("objectclass".into(), class))
+            .collect());
+
+        if let Some(user_filter) = &parameters.user_filter {
+            And(vec![Verbatim(user_filter), attr_wildcards, user_classes])
+        } else {
+            And(vec![attr_wildcards, user_classes])
+        }
+        .to_string()
+    }
 }
 
 #[allow(dead_code)]
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 5/6] ldap: tests: add LDAP integration tests
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
                   ` (3 preceding siblings ...)
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 4/6] ldap: allow searching for LDAP entities Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 6/6] ldap: add debian packaging Lukas Wagner
  2023-02-08 13:32 ` [pbs-devel] applied-series: [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Wolfgang Bumiller
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

This commit adds integration tests to ensure that the crate works as intended.
The tests are executed against a real LDAP server, namely `glauth`. `glauth` was
chosen because it ships as a single, statically compiled binary and can
be configured with a single configuration file.

The tests are written as off-the-shelf unit tests. However, they are
 #[ignored] by default, as they have some special requirements:
   * They required the GLAUTH_BIN environment variable to be set,
     pointing to the location of the `glauth` binary. `glauth` will be
     started and stopped automatically by the test suite.
   * Tests have to be executed sequentially (`--test-threads 1`),
     otherwise multiple instances of the glauth server might bind to the
     same port.

The `run_integration_tests.sh` checks whether GLAUTH_BIN is set, or if
not, attempts to find `glauth` on PATH. The script also ensures that the
tests are run sequentially.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 proxmox-ldap/Cargo.toml                       |   2 +
 proxmox-ldap/run_integration_tests.sh         |  31 ++++
 proxmox-ldap/src/lib.rs                       |   2 +-
 .../tests/assets/generate_certificate.sh      |   4 +
 proxmox-ldap/tests/assets/glauth.cfg          |  67 +++++++
 proxmox-ldap/tests/assets/glauth.crt          |  29 +++
 proxmox-ldap/tests/assets/glauth.key          |  52 ++++++
 proxmox-ldap/tests/assets/glauth_v6.cfg       |  67 +++++++
 proxmox-ldap/tests/glauth.rs                  | 166 ++++++++++++++++++
 9 files changed, 419 insertions(+), 1 deletion(-)
 create mode 100755 proxmox-ldap/run_integration_tests.sh
 create mode 100755 proxmox-ldap/tests/assets/generate_certificate.sh
 create mode 100644 proxmox-ldap/tests/assets/glauth.cfg
 create mode 100644 proxmox-ldap/tests/assets/glauth.crt
 create mode 100644 proxmox-ldap/tests/assets/glauth.key
 create mode 100644 proxmox-ldap/tests/assets/glauth_v6.cfg
 create mode 100644 proxmox-ldap/tests/glauth.rs

diff --git a/proxmox-ldap/Cargo.toml b/proxmox-ldap/Cargo.toml
index 70fba73..02cdb14 100644
--- a/proxmox-ldap/Cargo.toml
+++ b/proxmox-ldap/Cargo.toml
@@ -14,3 +14,5 @@ ldap3 = { workspace = true, default_features = false, features = ["tls"] }
 serde = { workspace = true, features = ["derive"] }
 native-tls.workspace = true
 
+[dev_dependencies]
+proxmox-async.workspace = true
diff --git a/proxmox-ldap/run_integration_tests.sh b/proxmox-ldap/run_integration_tests.sh
new file mode 100755
index 0000000..03b6d9b
--- /dev/null
+++ b/proxmox-ldap/run_integration_tests.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Run integration tests for the proxmox_ldap crate.
+# At this time, the tests require `glauth` to be available,
+# either explicitly passed via $GLAUTH_PATH, or somewhere
+# on $PATH.
+#
+# Tested with glauth v2.1.0
+
+function run_tests {
+    # All tests that need glauth running are ignored, so
+    # that we can run `cargo test` without caring about them
+    # Also, only run on 1 thread, because otherwise
+    # glauth would need a separate port for each rurnning test
+    exec cargo test -- --ignored --test-threads 1
+}
+
+
+if [ -z ${GLAUTH_BIN+x} ];
+then
+    GLAUTH_BIN=$(command -v glauth)
+    if [ $? -eq 0 ] ;
+    then
+        export GLAUTH_BIN
+    else
+        echo "glauth not found in PATH"
+        exit 1
+    fi
+fi
+
+run_tests
diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs
index 903ce1a..3815b27 100644
--- a/proxmox-ldap/src/lib.rs
+++ b/proxmox-ldap/src/lib.rs
@@ -62,7 +62,7 @@ pub struct SearchParameters {
     pub user_filter: Option<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
 /// Single LDAP user search result
 pub struct SearchResult {
     /// The full user's domain
diff --git a/proxmox-ldap/tests/assets/generate_certificate.sh b/proxmox-ldap/tests/assets/generate_certificate.sh
new file mode 100755
index 0000000..0c15216
--- /dev/null
+++ b/proxmox-ldap/tests/assets/generate_certificate.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+openssl req -x509 -newkey rsa:4096 -keyout glauth.key -out glauth.crt -days 36500 -nodes -subj '/CN=localhost'
+
diff --git a/proxmox-ldap/tests/assets/glauth.cfg b/proxmox-ldap/tests/assets/glauth.cfg
new file mode 100644
index 0000000..7255169
--- /dev/null
+++ b/proxmox-ldap/tests/assets/glauth.cfg
@@ -0,0 +1,67 @@
+debug = true
+[ldap]
+  enabled = true
+  listen = "0.0.0.0:3893"
+
+[ldaps]
+  enabled = true
+  listen = "0.0.0.0:3894"
+  cert = "tests/assets/glauth.crt"
+  key = "tests/assets/glauth.key"
+
+
+
+[backend]
+  datastore = "config"
+  baseDN = "dc=example,dc=com"
+  nameformat = "cn"
+  groupformat = "ou"
+
+# to create a passSHA256:   echo -n "mysecret" | openssl dgst -sha256
+
+[[users]]
+  name = "test1"
+  givenname="Test 1"
+  sn="User"
+  mail = "test1@example.com"
+  uidnumber = 1001
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+[[users]]
+  name = "test2"
+  givenname="Test 2"
+  sn="User"
+  mail = "test2@example.com"
+  uidnumber = 1002
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+[[users]]
+  name = "test3"
+  givenname="Test 3"
+  sn="User"
+  mail = "test3@example.com"
+  uidnumber = 1003
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+
+[[users]]
+  name = "serviceuser"
+  mail = "serviceuser@example.com"
+  uidnumber = 1111
+  primarygroup = 1001
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+    [[users.capabilities]]
+    action = "search"
+    object = "*"
+
+[[groups]]
+  name = "testgroup"
+  gidnumber = 1000
+
+[[groups]]
+  name = "svcaccts"
+  gidnumber = 1001
+
diff --git a/proxmox-ldap/tests/assets/glauth.crt b/proxmox-ldap/tests/assets/glauth.crt
new file mode 100644
index 0000000..6c0fdb7
--- /dev/null
+++ b/proxmox-ldap/tests/assets/glauth.crt
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIFCzCCAvOgAwIBAgIUREnN1wK1O6wTcVxSk6d5o0yHwjswDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIzMDExNzEwMjEwNFoYDzIxMjIx
+MjI0MTAyMTA0WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC/6A+m00AXHsvecYiuzBLgL6laTCqjIXyoHKdI5znI
+YOCXokCFDQI/wct1tA3GlIGuezECAHr76uF5G6T6DbSnbzE6Jd7ElKbQhnypwJc1
+4JsB7SYZVAz+Pj7CkjqmIEjwWWIFqotIvT/GfasHva/fGKKHhGFiU9N1gPthmrDr
+vwr5q2b+2FxyzkBlHD0pdOIO/aXOjISkS0MqOHRmUuoADXPjonWe0ujF/oI33ZOm
+8Xw+bdTuHEpgaioDP5LaSGdK+Y/1lOFqt8/9W8sCmo/Q8cLH1wZlkycl4d4jeMtK
+BB/iPot8n+9uMtZCjHS8zmedZKoIFGlnfHYmP8ckY7pHXfNoun/sHB9kXHlx0Rv2
+NCnd/117oGv6FFEio6lf+11Cm6qNYOxzsoZY6VpozMrI9CDFrMGwzvUuirzMmJql
+0TgrWJQE43bo5Rxkdd30TuqghXV9ENwG1sXacun2GGxuc5F6QG6l6k5H0dOh3gGG
+v7NAPKuIRi7oDJ3+B8wS4PTnroqRGVoKX7luQj2JrAB7mt42fE1iL9eEIjsde678
+oomnoOVcgWOpJCzJ5J10cNddhBO1JGMN/iHULeHLKzxWBptZTYWIwKp5hgZCK1s+
+fiuvh8CRxiI5IwyDLlQbI4kf6iLxipFqIHNsddYRnzZIW/iogRfdd0iPvqorXucV
+tQIDAQABo1MwUTAdBgNVHQ4EFgQULLKz9hnIn5jU7ELlth4DXPhGrywwHwYDVR0j
+BBgwFoAULLKz9hnIn5jU7ELlth4DXPhGrywwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAOr9nQEvwNF2qcIteYV3WgRt//oSf/L+qYANVD86ZUK2c
+07zWL9mKxXwXCz1ncfjLMMb/ZO3gnrI2yW5MJdyHup+28ESqMmvNX1Sz9hkjqaP0
+irM3sD56FhjUYa8H8g3k1kKl7N4ogt0JZpwJt+JFoRHu1vaWKahOmn8owP789CPQ
+391zyhqZHOowmHSd0lu8AWSdIuzU5Q5Vk38+GW4gHAxsSG0hWempg3U7g9DOAXbe
+kFHSa8H4bpXMdwndZJPv+NhA8LYG5UC2aD86dUWXrfEMuXLqvxMpYIZ76A3UZUWl
++zmEKHmjCumz5WdjnPGIC/NJiREM6kUd/UqWv4XuPkgQjmnlsB4POKaV658uYFvF
+HnLPEkwT6jnJqyMH0YGhbwZdbS/UpARvoZ4ZkpKFVLB3SlG/8cn+YtXLMBwefAUr
+cAwzLCcu28LX/bq+65HldFxjYr2XddcbJErLvyfl5w/+UaqrCnSXiSL5yqxpcie6
+w3r+U5Ei51iJEJTJIabcQiH0+dkLNVRvaCywcChgUjHqNdTtZvGuvGiwvfJ3ItSI
+HkvcWug5pO+kyNmaDPLPYtL4mm9tIMgdIIwXZSHcOC459aVvh+5Mmqwat4Ijfa7+
+TBkr87t671pL1XH5eeeUKYbFpKq/ZxA+O8LT9IXeLyediLvcf7V5BqwuuPHkzHw=
+-----END CERTIFICATE-----
diff --git a/proxmox-ldap/tests/assets/glauth.key b/proxmox-ldap/tests/assets/glauth.key
new file mode 100644
index 0000000..d29e06a
--- /dev/null
+++ b/proxmox-ldap/tests/assets/glauth.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+yWQL2XczMt+NRQn+v23VKpQVK/R9PMEV
+-----END PRIVATE KEY-----
diff --git a/proxmox-ldap/tests/assets/glauth_v6.cfg b/proxmox-ldap/tests/assets/glauth_v6.cfg
new file mode 100644
index 0000000..c30107d
--- /dev/null
+++ b/proxmox-ldap/tests/assets/glauth_v6.cfg
@@ -0,0 +1,67 @@
+debug = true
+[ldap]
+  enabled = true
+  listen = "[::]:3893"
+
+[ldaps]
+  enabled = true
+  listen = "[::]:3894"
+  cert = "tests/assets/glauth.crt"
+  key = "tests/assets/glauth.key"
+
+
+
+[backend]
+  datastore = "config"
+  baseDN = "dc=example,dc=com"
+  nameformat = "cn"
+  groupformat = "ou"
+
+# to create a passSHA256:   echo -n "mysecret" | openssl dgst -sha256
+
+[[users]]
+  name = "test1"
+  givenname="Test 1"
+  sn="User"
+  mail = "test1@example.com"
+  uidnumber = 1001
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+[[users]]
+  name = "test2"
+  givenname="Test 2"
+  sn="User"
+  mail = "test2@example.com"
+  uidnumber = 1002
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+[[users]]
+  name = "test3"
+  givenname="Test 3"
+  sn="User"
+  mail = "test3@example.com"
+  uidnumber = 1003
+  primarygroup = 1000
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+
+
+[[users]]
+  name = "serviceuser"
+  mail = "serviceuser@example.com"
+  uidnumber = 1111
+  primarygroup = 1001
+  passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # password
+    [[users.capabilities]]
+    action = "search"
+    object = "*"
+
+[[groups]]
+  name = "testgroup"
+  gidnumber = 1000
+
+[[groups]]
+  name = "svcaccts"
+  gidnumber = 1001
+
diff --git a/proxmox-ldap/tests/glauth.rs b/proxmox-ldap/tests/glauth.rs
new file mode 100644
index 0000000..a45e992
--- /dev/null
+++ b/proxmox-ldap/tests/glauth.rs
@@ -0,0 +1,166 @@
+use std::{
+    process::{Child, Command, Stdio},
+    thread::sleep,
+    time::Duration,
+};
+
+use anyhow::{Context, Error};
+use proxmox_ldap::*;
+
+struct GlauthServer {
+    handle: Child,
+}
+
+impl GlauthServer {
+    fn new(path: &str) -> Result<Self, Error> {
+        let glauth_bin = std::env::var("GLAUTH_BIN").context("GLAUTH_BIN is not set")?;
+        let handle = Command::new(&glauth_bin)
+            .arg("-c")
+            .arg(path)
+            .stdin(Stdio::null())
+            .stdout(Stdio::null())
+            .stderr(Stdio::null())
+            .spawn()
+            .context("Could not start glauth process")?;
+
+        // Make 'sure' that glauth is up
+        sleep(Duration::from_secs(1));
+
+        Ok(Self { handle })
+    }
+}
+
+impl Drop for GlauthServer {
+    fn drop(&mut self) {
+        self.handle.kill().ok();
+    }
+}
+
+fn authenticate(con: &LdapConnection, user: &str, pass: &str) -> Result<(), Error> {
+    proxmox_async::runtime::block_on(con.authenticate_user(user, pass))
+}
+
+fn default_config() -> LdapConfig {
+    LdapConfig {
+        servers: vec!["localhost".into()],
+        port: Some(3893),
+        user_attr: "cn".into(),
+        base_dn: "dc=example,dc=com".into(),
+        bind_dn: Some("cn=serviceuser,ou=svcaccts,dc=example,dc=com".into()),
+        bind_password: Some("password".into()),
+        tls_mode: LdapConnectionMode::Ldap,
+        verify_certificate: false,
+        additional_trusted_certificates: None,
+        certificate_store_path: Some("/etc/ssl/certs".into()),
+    }
+}
+
+#[test]
+#[ignore]
+fn test_authentication() -> Result<(), Error> {
+    let _glauth = GlauthServer::new("tests/assets/glauth.cfg")?;
+
+    let connection = LdapConnection::new(default_config());
+
+    assert!(authenticate(&connection, "test1", "password").is_ok());
+    assert!(authenticate(&connection, "test2", "password").is_ok());
+    assert!(authenticate(&connection, "test3", "password").is_ok());
+    assert!(authenticate(&connection, "test1", "invalid").is_err());
+    assert!(authenticate(&connection, "invalid", "password").is_err());
+
+    Ok(())
+}
+
+#[test]
+#[ignore]
+fn test_authentication_via_ipv6() -> Result<(), Error> {
+    let _glauth = GlauthServer::new("tests/assets/glauth_v6.cfg")?;
+
+    let settings = LdapConfig {
+        servers: vec!["[::1]".into()],
+        ..default_config()
+    };
+
+    let connection = LdapConnection::new(settings);
+
+    assert!(authenticate(&connection, "test1", "password").is_ok());
+
+    Ok(())
+}
+
+#[test]
+#[ignore]
+fn test_authentication_via_ldaps() -> Result<(), Error> {
+    let settings = LdapConfig {
+        port: Some(3894),
+        tls_mode: LdapConnectionMode::Ldaps,
+        verify_certificate: true,
+        additional_trusted_certificates: Some(vec!["tests/assets/glauth.crt".into()]),
+        ..default_config()
+    };
+
+    let _glauth = GlauthServer::new("tests/assets/glauth.cfg")?;
+
+    let connection = LdapConnection::new(settings);
+
+    assert!(authenticate(&connection, "test1", "password").is_ok());
+    assert!(authenticate(&connection, "test1", "invalid").is_err());
+
+    Ok(())
+}
+
+#[test]
+#[ignore]
+fn test_fallback() -> Result<(), Error> {
+    let settings = LdapConfig {
+        servers: vec!["invalid.host".into(), "localhost".into()],
+        ..default_config()
+    };
+
+    let _glauth = GlauthServer::new("tests/assets/glauth.cfg")?;
+
+    let connection = LdapConnection::new(settings);
+    assert!(authenticate(&connection, "test1", "password").is_ok());
+
+    Ok(())
+}
+
+#[test]
+#[ignore]
+fn test_search() -> Result<(), Error> {
+    let _glauth = GlauthServer::new("tests/assets/glauth.cfg")?;
+
+    let connection = LdapConnection::new(default_config());
+
+    let params = SearchParameters {
+        attributes: vec!["cn".into(), "mail".into(), "sn".into()],
+        user_classes: vec!["posixAccount".into()],
+        user_filter: Some("(cn=test*)".into()),
+    };
+
+    let search_results = proxmox_async::runtime::block_on(connection.search_entities(&params))?;
+
+    assert_eq!(search_results.len(), 3);
+
+    for a in search_results {
+        assert!(a.dn.starts_with("cn=test"));
+        assert!(a.dn.ends_with("ou=testgroup,ou=users,dc=example,dc=com"));
+
+        assert!(a
+            .attributes
+            .get("mail")
+            .unwrap()
+            .get(0)
+            .unwrap()
+            .ends_with("@example.com"));
+        assert!(a
+            .attributes
+            .get("sn")
+            .unwrap()
+            .get(0)
+            .unwrap()
+            .eq("User".into()));
+    }
+
+    Ok(())
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [PATCH v3 proxmox 6/6] ldap: add debian packaging
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
                   ` (4 preceding siblings ...)
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 5/6] ldap: tests: add LDAP integration tests Lukas Wagner
@ 2023-01-24 10:03 ` Lukas Wagner
  2023-02-08 13:32 ` [pbs-devel] applied-series: [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Wolfgang Bumiller
  6 siblings, 0 replies; 8+ messages in thread
From: Lukas Wagner @ 2023-01-24 10:03 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 proxmox-ldap/debian/changelog     |  5 ++++
 proxmox-ldap/debian/control       | 43 +++++++++++++++++++++++++++++++
 proxmox-ldap/debian/copyright     | 16 ++++++++++++
 proxmox-ldap/debian/debcargo.toml |  7 +++++
 4 files changed, 71 insertions(+)
 create mode 100644 proxmox-ldap/debian/changelog
 create mode 100644 proxmox-ldap/debian/control
 create mode 100644 proxmox-ldap/debian/copyright
 create mode 100644 proxmox-ldap/debian/debcargo.toml

diff --git a/proxmox-ldap/debian/changelog b/proxmox-ldap/debian/changelog
new file mode 100644
index 0000000..3812057
--- /dev/null
+++ b/proxmox-ldap/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-ldap (0.1.0-1) stable; urgency=medium
+
+  * Initial release.
+
+ --  Proxmox Support Team <support@proxmox.com>  Thu, 12 Jan 2023 11:42:11 +0200
diff --git a/proxmox-ldap/debian/control b/proxmox-ldap/debian/control
new file mode 100644
index 0000000..783c4d4
--- /dev/null
+++ b/proxmox-ldap/debian/control
@@ -0,0 +1,43 @@
+Source: rust-proxmox-ldap
+Section: rust
+Priority: optional
+Build-Depends: debhelper (>= 12),
+ dh-cargo (>= 25),
+ cargo:native <!nocheck>,
+ rustc:native <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-anyhow-1+default-dev <!nocheck>,
+ librust-ldap3-0.11+default-dev <!nocheck>,
+ librust-ldap3-0.11+tls-dev <!nocheck>,
+ librust-native-tls-0.2+default-dev <!nocheck>,
+ librust-serde-1+default-dev <!nocheck>,
+ librust-serde-1+derive-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.6.1
+Vcs-Git: git://git.proxmox.com/git/proxmox-ldap.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox-ldap.git
+X-Cargo-Crate: proxmox-ldap
+Rules-Requires-Root: no
+
+Package: librust-proxmox-ldap-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-anyhow-1+default-dev,
+ librust-ldap3-0.11+default-dev,
+ librust-ldap3-0.11+tls-dev,
+ librust-native-tls-0.2+default-dev,
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev
+Provides:
+ librust-proxmox-ldap+default-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0+default-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0.1-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-ldap-0.1.0+default-dev (= ${binary:Version})
+Description: Proxmox library for LDAP authentication/synchronization - Rust source code
+ This package contains the source for the Rust proxmox-ldap crate, packaged by
+ debcargo for use with cargo and dh-cargo.
diff --git a/proxmox-ldap/debian/copyright b/proxmox-ldap/debian/copyright
new file mode 100644
index 0000000..4fce23a
--- /dev/null
+++ b/proxmox-ldap/debian/copyright
@@ -0,0 +1,16 @@
+Copyright (C) 2023 Proxmox Server Solutions GmbH
+
+This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/proxmox-ldap/debian/debcargo.toml b/proxmox-ldap/debian/debcargo.toml
new file mode 100644
index 0000000..ec498a1
--- /dev/null
+++ b/proxmox-ldap/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
-- 
2.30.2





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] applied-series: [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate
  2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
                   ` (5 preceding siblings ...)
  2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 6/6] ldap: add debian packaging Lukas Wagner
@ 2023-02-08 13:32 ` Wolfgang Bumiller
  6 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2023-02-08 13:32 UTC (permalink / raw)
  To: Lukas Wagner; +Cc: pbs-devel

applied series, with some followups:
- dropped the Ldap prefixes (it was mixed with&without and we repeat so
  much already anyway)
- default-features needs to go to the toplevel Cargo.toml
- git repo in d/control was still the separate one

On Tue, Jan 24, 2023 at 11:03:31AM +0100, Lukas Wagner wrote:
> This patch series adds the new `proxmox-ldap` crate. The crate is mostly based on
> `src/server/ldap.rs` from [1].
> 
> The main reason for breaking this out into a separate crate is to make it easily
> reusable from PVE/PMG via perlmod -- at some point in the future, all
> products could use the same LDAP implemenation.
> 
> This is sent as a separate patch series, as the original [1] was already
> quite large with 17 commits, affecting multiple repositories.
> 
> 
> Changes v2 -> v3:
>   * Move the `proxmox-ldap` crate into the `proxmox` repo
>   * Let FilterElement take &str instead of String
>   * Implement the Display trait instead of ToString
> 
> Changes [1]@v1 -> v2:
>   * Change how custom TLS-certificates work:
>     Pass certificate paths instead of strings containing the
>     certificate.
>     Now, users of this crate can pass additional root certs that
>     are to be trusted. Alternatively, and this was added with PVE
>     compatibility in mind, one can add whole certificate store
>     directories, replacing the system's default at `/etc/ssl/certs`.
> 
>   * Add integration tests, testing the implementation against a real
>     LDAP server (`glauth`). The test can be executed via the
>     `run_integratin_tests.sh` helper and require the `glauth` binary to
>     be available. The integration tests are #[ignored] by default, so they
>     don't interfere with regular unit-test execution.
> 
> Thanks to Wolfgang and Thomas for their review of v2.
> 
> [1] https://lists.proxmox.com/pipermail/pbs-devel/2023-January/005788.html
> 
> Lukas Wagner (6):
>   ldap: create new `proxmox-ldap` crate
>   ldap: add basic user auth functionality
>   ldap: add helpers for constructing LDAP filters
>   ldap: allow searching for LDAP entities
>   ldap: tests: add LDAP integration tests
>   ldap: add debian packaging
> 
>  Cargo.toml                                    |   3 +
>  proxmox-ldap/Cargo.toml                       |  18 +
>  proxmox-ldap/debian/changelog                 |   5 +
>  proxmox-ldap/debian/control                   |  43 ++
>  proxmox-ldap/debian/copyright                 |  16 +
>  proxmox-ldap/debian/debcargo.toml             |   7 +
>  proxmox-ldap/run_integration_tests.sh         |  31 ++
>  proxmox-ldap/src/lib.rs                       | 390 ++++++++++++++++++
>  .../tests/assets/generate_certificate.sh      |   4 +
>  proxmox-ldap/tests/assets/glauth.cfg          |  67 +++
>  proxmox-ldap/tests/assets/glauth.crt          |  29 ++
>  proxmox-ldap/tests/assets/glauth.key          |  52 +++
>  proxmox-ldap/tests/assets/glauth_v6.cfg       |  67 +++
>  proxmox-ldap/tests/glauth.rs                  | 166 ++++++++
>  14 files changed, 898 insertions(+)
>  create mode 100644 proxmox-ldap/Cargo.toml
>  create mode 100644 proxmox-ldap/debian/changelog
>  create mode 100644 proxmox-ldap/debian/control
>  create mode 100644 proxmox-ldap/debian/copyright
>  create mode 100644 proxmox-ldap/debian/debcargo.toml
>  create mode 100755 proxmox-ldap/run_integration_tests.sh
>  create mode 100644 proxmox-ldap/src/lib.rs
>  create mode 100755 proxmox-ldap/tests/assets/generate_certificate.sh
>  create mode 100644 proxmox-ldap/tests/assets/glauth.cfg
>  create mode 100644 proxmox-ldap/tests/assets/glauth.crt
>  create mode 100644 proxmox-ldap/tests/assets/glauth.key
>  create mode 100644 proxmox-ldap/tests/assets/glauth_v6.cfg
>  create mode 100644 proxmox-ldap/tests/glauth.rs
> 
> -- 
> 2.30.2




^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-02-08 13:32 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-24 10:03 [pbs-devel] [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 1/6] ldap: create new `proxmox-ldap` crate Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 2/6] ldap: add basic user auth functionality Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 3/6] ldap: add helpers for constructing LDAP filters Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 4/6] ldap: allow searching for LDAP entities Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 5/6] ldap: tests: add LDAP integration tests Lukas Wagner
2023-01-24 10:03 ` [pbs-devel] [PATCH v3 proxmox 6/6] ldap: add debian packaging Lukas Wagner
2023-02-08 13:32 ` [pbs-devel] applied-series: [PATCH v3 proxmox 0/6] introduce proxmox-ldap crate Wolfgang Bumiller

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal