Skip to content

Commit

Permalink
feat: support setting openssl certs for openldap
Browse files Browse the repository at this point in the history
  • Loading branch information
guenhter committed Sep 26, 2024
1 parent e2897aa commit e3d7fa3
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 5 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ k8s-openapi = { version = "0.21.1", features = ["v1_29"] }
clickhouse = "0.11.6"
vaultrs = "0.7.2"
openssl-sys = { version = "0.9.103", features = ["vendored"] }

[patch.crates-io]
testcontainers = { git = "https://github.com/guenhter/testcontainers-rs.git", branch = "release-0.23.0" }
native-tls = { version = "0.2.12", features = ["vendored"] }

[[example]]
name = "postgres"
Expand Down
17 changes: 17 additions & 0 deletions src/openldap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OpenLDAP Testcontainer

This modules provides the `bitnami/openldap` image as a testcontainer.


## Testing

For testing tls connections, you need to provide a certificate and key. All needed tls artifacts are generated by the `generate-certs.sh` script.

The used openssl version for the latests created certificate is:

```bash
> openssl version
OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
```

Please update the version here in the README if you generate new certificates for the tests.
53 changes: 53 additions & 0 deletions src/openldap/create_certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash

## Helper Script for creating certificates for the LDAP server tls tests

# Set variables
ROOT_SUBJECT="/C=AT/O=Test/OU=Testcontainer/CN=rootCA"
SERVER_SUBJECT="/C=AT/O=Test/OU=Testcontainer/CN=ldap.example.org"
DAYS_UNTIL_EXPIRE=$((365 * 20))

# Create directory for certificates
mkdir -p certs
rm -fr certs/*
cd certs

# Create a config file for the server certificate
cat >server.cnf <<EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = AT
O = Test
OU = Testcontainer
CN = ldap.example.org
[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = openldap
EOF

# Create a self-signed root certificate
openssl req -x509 -nodes -days $DAYS_UNTIL_EXPIRE -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt -subj "$ROOT_SUBJECT"

# Create a key for the server certificate
openssl genrsa -out ldap.example.org.key 2048

# Generate Server CSR using the config file
openssl req -new -key ldap.example.org.key -out ldap.example.org.csr -subj "$SERVER_SUBJECT"

# Sign the server certificate with the Root CA
openssl x509 -req -in ldap.example.org.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ldap.example.org.crt -days $DAYS_UNTIL_EXPIRE -extfile server.cnf -extensions v3_req

# Clean up
rm -r *.csr *.cnf *.srl rootCA.key

cd -
150 changes: 148 additions & 2 deletions src/openldap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use testcontainers::{
const NAME: &str = "bitnami/openldap";
const TAG: &str = "2.6.8";
const OPENLDAP_PORT: ContainerPort = ContainerPort::Tcp(1389);
const OPENLDAPS_PORT: ContainerPort = ContainerPort::Tcp(1636);

/// Module to work with [`OpenLDAP`] inside of tests.
///
Expand Down Expand Up @@ -244,6 +245,41 @@ impl OpenLDAP {
.push(CopyToContainer::new(source, container_ldif_path));
self
}

/// Set all necessary certificate artifacts to build up a secure communication.
/// Default: `[]`
pub fn with_tls(
mut self,
cert: impl Into<CopyDataSource>,
key: impl Into<CopyDataSource>,
) -> Self {
self.copy_to_sources
.push(CopyToContainer::new(cert, "/certs/cert.crt"));
self.copy_to_sources
.push(CopyToContainer::new(key, "/certs/key.key"));

self.env_vars.insert(
"LDAP_TLS_CERT_FILE".to_owned(),
"/certs/cert.crt".to_owned(),
);
self.env_vars
.insert("LDAP_TLS_KEY_FILE".to_owned(), "/certs/key.key".to_owned());

self.env_vars
.insert("LDAP_ENABLE_TLS".to_owned(), "yes".to_owned());

self
}

/// Sets the root certificate used for signing the tls certificate.
/// Default: `[]`
pub fn with_cert_ca(mut self, ca: impl Into<CopyDataSource>) -> Self {
self.copy_to_sources
.push(CopyToContainer::new(ca, "/certs/ca.crt"));
self.env_vars
.insert("LDAP_TLS_CA_FILE".to_owned(), "/certs/ca.crt".to_owned());
self
}
}

/// hash to be used in generation of user passwords.
Expand Down Expand Up @@ -408,7 +444,7 @@ impl Image for OpenLDAP {
}

fn expose_ports(&self) -> &[ContainerPort] {
&[OPENLDAP_PORT]
&[OPENLDAP_PORT, OPENLDAPS_PORT]
}

fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
Expand Down Expand Up @@ -549,6 +585,7 @@ mod tests {

#[tokio::test]
async fn ldap_with_ldif() -> Result<(), Box<dyn std::error::Error + 'static>> {
let _ = pretty_env_logger::try_init();
let ldif = r#"
version: 1
Expand Down Expand Up @@ -579,7 +616,6 @@ uidnumber: 1001
userpassword: {SSHA}1vtVpqX6Y77jAVs/1uTd/rHS8YRYEh/9
"#;

let _ = pretty_env_logger::try_init();
let openldap_image = OpenLDAP::default().with_ldif_file(ldif.to_string().into_bytes());

let node = openldap_image.start().await?;
Expand All @@ -599,4 +635,114 @@ userpassword: {SSHA}1vtVpqX6Y77jAVs/1uTd/rHS8YRYEh/9
ldap.unbind().await?;
Ok(())
}

#[tokio::test]
async fn ldap_secure() -> Result<(), Box<dyn std::error::Error + 'static>> {
let _ = pretty_env_logger::try_init();

let root_ca = r#"-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUacPFMoNlemFtn974hn0x4c+ssGowDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVQxDTALBgNVBAoMBFRlc3QxFjAUBgNVBAsMDVRlc3Rj
b250YWluZXIxDzANBgNVBAMMBnJvb3RDQTAeFw0yNDA5MjUxODI0MDFaFw00NDA5
MjAxODI0MDFaMEUxCzAJBgNVBAYTAkFUMQ0wCwYDVQQKDARUZXN0MRYwFAYDVQQL
DA1UZXN0Y29udGFpbmVyMQ8wDQYDVQQDDAZyb290Q0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC4+4uW9PE5HQGWtn4/P1wPm11z/UevNGveDEUvwcDz
oEZlfRLTihoVfc7EZGUF8/6TS2rkp1qz2yrDDP4o7a7l0Om22vCshsWlhhqQ1mON
tiXnOWXqKjon6NZatvYnQVvUwm8BuSi4HR4xNM8N9BF81rhFj3tbam3lylJign/3
sFsxUg2C3mAaDoolTFslBPPnKHW2EmSw04kgC6XhWGXqpAbWbL5h2rlOE/WoLTkl
f+EpILsCyEApNbVLXh+B9+IrJvNqX6yuFL7zzJhefOa1L1sAG0bNc9nAwuwyDcDu
OzxULkv0JGK2Dob6NL74NSB96kPLbZi/FRIHwhBgmUlPAgMBAAGjUzBRMB0GA1Ud
DgQWBBQ2Bcju3oNHso17GQFoKd5/46OsnjAfBgNVHSMEGDAWgBQ2Bcju3oNHso17
GQFoKd5/46OsnjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAP
za/O0NzyLZjjOoBsDKyJzvgHv/Ls8yBRSmwAbJoXqT1F4F39px5ZVOdmruXeJsG1
PIaLb2/4oPoSgqLfiPUMAJelTjD/CVpfFthiorCgYPSIIhkjgc6jdhCez67gotFE
nBITxIRg47yAmCA+0+/YtTnul1deB9r2cuXeHVTPfUVphDsKSGVLJS0TK1iIL/PZ
k/bSZzdk/i80spmFS3W/fLHPWFUbio1r0CBpbibQNl19x8uHz+J7L1kmW7gofyd9
w4a3nYIMqapx5KGgGqI3lyc/ePet2JhUabbu7rQx6oEHJaaco3qip2t6pS0WtDcG
B5tEQNlAfsx/Rfi4JUNi
-----END CERTIFICATE-----
"#;
let key = r#"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDB8KR+QtENXXI0
+BWZ/rD2nDfRi7v6dDLpD1yuVLFbBgVMDisf8CoqZP67x9lW5sRz/CXLjY+If1Vi
xFxcwURzVBWmrMBLbEgDnsyX0GhgCdZTJM3eBTXwCN77bH/xSkZ6Dg1AV/i/ltAI
TvMlE5Azrd/0IePWEQYtQYUQMlJHcxrDex3sKZ/oM6ohMl1sbkNDV+Ncatl7bIel
RDyHE1SgExFFL2qWg2CeHJHPliq/0osp1cU99SCtaalxwisnih0GMswIf++1s+r6
/CGRI+5xXOd1wtPwcjFxdVnSuhbFxniz9fPaPXw0NoVBksRcJNri2RHDZpW7nK+D
OMcvWtxxAgMBAAECggEANQPOQ4GKWgfwX1Btv0HjKBa+H3b+NNGs1Q7Q/ArEzKgR
rJ+25C0nqZ0gET7pR5sfmsETp9gTo3GDatNYmDZwusIChSR2EGgSK4MuVFWxIoet
4d6OtCFihDI4miwnsVLnfxf2QV+K7PyR86N5TepSIf5m2PqmqG7Q7HAbqrjGyybO
te4IIYUox2OirICfNMdy6ROj3c+oEiXu5eEpxbl01WzSU7rO4fMdfc0O/cocg2rr
njBXD0utCiUw0M6EaisYzAg5ughP02enmxGzGwBT+lr2AQHG3Mk++PICRxsiBIv0
T0o6Q3mIR4YMNDt7dZVz8mjL7lx1iuczM5CUbRnhzQKBgQDxTubdOmTidd1WxRJ5
4t19Ga604YVG8c97hZ7fpNL0fTY/JcxkEN02uhHgA9fmBhE2vRus82x+iLKjcNBY
ZKzM2PaRsjK2g9UNi829Zii/VjZJX1QwipO0EQt0Rruf+QtWXL47UHKTxpRfLrFN
zaXrVM15uq/pdhmovj+LQ4NplwKBgQDNv3JQVXBImy9/5h2xPwVlkjXzQdD0PTEs
emnLRfnctyqAzmtgTsbS1tDhoCwm5TuI6MMbrtnojSQKRr68BDfhvnm4TBHWYrS1
0BqY+P1XkJH4jmIQsRpS/ZnDlB50+aiAnuMahui0mUT71/upGS277Zqr+u7Gkt2b
fFeWzu3bNwKBgFiU+FbZ6tLfJaOGsKOhzmDwHpwz9XL3rYzQnmPG49HwbQt9WqyZ
LDu8zncHsie0rnkDrrcsnPVORRWOgk0QmAaS1uDhI5CwkHNqkNooOGkUwtToc8Vl
+Zaucx/6H0I4cBsB7KtlesoYqbrPLzM6fOAIv20iRRVUz1KMlFMRM5p9AoGBAJJr
Tv/Kfbi974S2j6Tms4GAFrLBwOE/dvIvP4CwkMs48p9txs5n4WiEBWy73w/jDIY3
FzppKZwsbVx+0hfdbKNTOS4lvH/0CKRmr7bzYt9g+/CF61Xzo0cyQK4Fh9M5JGg8
KmRjY9G6TXRoVSkWyQw3YF5JmoloVRrk1zR0mKLrAoGBAKPJmagtBVmTvKdYFhs0
X6au0mG5xN4HnkdvX1sGgeg+DgJ/dCa4LA3lZagUhDjupR0QgaaYVZhyFe6lDVck
lOB14QZNoFr+xwiSDg4oM4/TQXualO/4nZxzUynbZNdNuvhQ8mRdeu5jqs363deP
bCWwJaA6QQpNSitVrzg5XbRQ
-----END PRIVATE KEY-----
"#;
let cert = r#"-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIUJArHsm2jdPlyOXWC7TWdFxYD19kwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVQxDTALBgNVBAoMBFRlc3QxFjAUBgNVBAsMDVRlc3Rj
b250YWluZXIxDzANBgNVBAMMBnJvb3RDQTAeFw0yNDA5MjUxODI0MDJaFw00NDA5
MjAxODI0MDJaME8xCzAJBgNVBAYTAkFUMQ0wCwYDVQQKDARUZXN0MRYwFAYDVQQL
DA1UZXN0Y29udGFpbmVyMRkwFwYDVQQDDBBsZGFwLmV4YW1wbGUub3JnMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfCkfkLRDV1yNPgVmf6w9pw30Yu7
+nQy6Q9crlSxWwYFTA4rH/AqKmT+u8fZVubEc/wly42PiH9VYsRcXMFEc1QVpqzA
S2xIA57Ml9BoYAnWUyTN3gU18Aje+2x/8UpGeg4NQFf4v5bQCE7zJROQM63f9CHj
1hEGLUGFEDJSR3Maw3sd7Cmf6DOqITJdbG5DQ1fjXGrZe2yHpUQ8hxNUoBMRRS9q
loNgnhyRz5Yqv9KLKdXFPfUgrWmpccIrJ4odBjLMCH/vtbPq+vwhkSPucVzndcLT
8HIxcXVZ0roWxcZ4s/Xz2j18NDaFQZLEXCTa4tkRw2aVu5yvgzjHL1rccQIDAQAB
o4GIMIGFMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAeBgNV
HREEFzAVgglsb2NhbGhvc3SCCG9wZW5sZGFwMB0GA1UdDgQWBBTOko7NFMj7/fd/
KGvfbYezhjmH8zAfBgNVHSMEGDAWgBQ2Bcju3oNHso17GQFoKd5/46OsnjANBgkq
hkiG9w0BAQsFAAOCAQEAlud2ST43+Q2Sa+VHS8Tio1M76+AdNj1dmQHYsFN7Vm91
cAEOFOO8y/oSqTZrIuxenFCIsMeAAVOEZ7BjcpzX50ncHAYDu2szpmTscvujNoSs
1qvbfRC1aL8bky4XECct7Md1h7TTN/pY0E+6b1wI0gyHkCeuiaOfeq7I+lUogIzL
SpuBTQvi59BdeLyTXImg8WCSKoLrZljdaEjCZdM51FWFvY2WdW1NE/ahniJpkGv5
hcDj6qNPn8FHCLxzOs1HUucyncxbS9z6I91WaFWXWu0DH90lMA8gedyXZr6YOnkg
H32P9zbIKaSiPxFg5JVRW5hpQWUI1dYr3CpKP4i98w==
-----END CERTIFICATE-----
"#;

let openldap_image = OpenLDAP::default()
.with_allow_anon_binding(false)
.with_user("maximiliane", "pwd1")
.with_tls(cert.to_string().into_bytes(), key.to_string().into_bytes())
.with_cert_ca(root_ca.to_string().into_bytes());
let node = openldap_image.start().await?;

let connection_string = format!(
"ldaps://{}:{}",
node.get_host().await?,
node.get_host_port_ipv4(OPENLDAPS_PORT).await?,
);

let mut builder = native_tls::TlsConnector::builder();
let root_ca = native_tls::Certificate::from_pem(root_ca.as_bytes())?;
let connector = builder.add_root_certificate(root_ca).build()?;

let settings = ldap3::LdapConnSettings::new().set_connector(connector);
let (conn, mut ldap) =
ldap3::LdapConnAsync::with_settings(settings, &connection_string).await?;

ldap3::drive!(conn);
ldap.simple_bind("cn=maximiliane,ou=users,dc=example,dc=org", "pwd1")
.await?
.success()?;
let users = read_users(&mut ldap, "(cn=*)", &["cn"]).await?;
assert_eq!(users.len(), 2); // cn=maximiliane and cn=readers
ldap.unbind().await?;
Ok(())
}
}

0 comments on commit e3d7fa3

Please # to comment.