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 25, 2024
1 parent 6e22a74 commit af983e0
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 4 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
50 changes: 50 additions & 0 deletions src/openldap/create_certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash

# Set variables
ROOT_SUBJECT="/C=AT/O=Test/OU=Testcontainer/CN=rootCA"
SERVER_SUBJECT="/C=AT/O=Test/OU=Testcontainer/CN=ldap.example.org"

# 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 365 -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 365 -extfile server.cnf -extensions v3_req

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

cd -
145 changes: 144 additions & 1 deletion 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,36 @@ 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_certs(
mut self,
cert: impl Into<CopyDataSource>,
key: impl Into<CopyDataSource>,
ca: 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.copy_to_sources
.push(CopyToContainer::new(ca, "/certs/ca.crt"));

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_TLS_CA_FILE".to_owned(), "/certs/ca.crt".to_owned());

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

self
}
}

/// hash to be used in generation of user passwords.
Expand Down Expand Up @@ -408,7 +439,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 @@ -600,4 +631,116 @@ userpassword: {SSHA}1vtVpqX6Y77jAVs/1uTd/rHS8YRYEh/9
ldap.unbind().await?;
Ok(())
}

#[tokio::test]
async fn ldap_secure() -> Result<(), Box<dyn std::error::Error + 'static>> {
let root_ca = r#"-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUJOEGi61FLb02VN7OyUj8WQwldEMwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVQxDTALBgNVBAoMBFRlc3QxFjAUBgNVBAsMDVRlc3Rj
b250YWluZXIxDzANBgNVBAMMBnJvb3RDQTAeFw0yNDA5MjQwNjQzMzlaFw0yNTA5
MjQwNjQzMzlaMEUxCzAJBgNVBAYTAkFUMQ0wCwYDVQQKDARUZXN0MRYwFAYDVQQL
DA1UZXN0Y29udGFpbmVyMQ8wDQYDVQQDDAZyb290Q0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCI6E4qyB4jS0MDCEoREXpy93qQCYDVgkYIolmNXgKQ
PcDr+SLPifg0yybdaERFM2RFXfnOKKOIL/SWlXFMm4hlk9s2WPNVdeGmHpIcEeUH
nkw5mVIm0Bn5PN7P5GCMYrKd3sjxSBT54sQaFzOO8xUP+pGQFmWWxu89TZtusgL7
WaEGWZng5XyjmFUNjzS8ppG19DP6FsfwkH+X//5DFc0rnQYI+vofpxe153wNYKYU
Gw6fpOvl32xkNvm0N12RV5vI0y6tDe6Mprkl4NfOSkaUv2jbQ+AwSLwfBvkXLFnK
pBF/uxEAl5LTfhaDTtbXF9X3FrVB5+kRteqg4LcFsD13AgMBAAGjUzBRMB0GA1Ud
DgQWBBTwYJpbDj3GrICSwAQj4TbBMXp46DAfBgNVHSMEGDAWgBTwYJpbDj3GrICS
wAQj4TbBMXp46DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBs
dSEzbRDQEGcD2WF+LWz0f+qYmpQZmuorhWhBMx/vsBkARVgXhO/8zYskEV3Q7JVJ
kd4b/AOrSSo8RKfn1y+ahznjlEeC0yqHl1COq+B7lc875TmOLXVNwas7QRiGU9d1
qWmAxXV68a6KhnzWJo2YaD+QOXp3CKVgIPdYx/p5W5pESQYDEjD54SPMqj2q80iC
y5I6hJQRhE9zs3QAhA8jO8zhLo62QooWPnmToTXUXxxVhcNh5RPpUx6kY12lALim
yEd4SHpM1xUaMN6rjJLNFfU+KSQawPljBrdpiIKR/JejJTmGQ//FhMZRbF2WiBVc
vJf0O9rm9ygB3kD1Gvjc
-----END CERTIFICATE-----
"#;
let key = r#"-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCq5DVU3Q1UlJVu
swYq87ObR2vl+4NcVcZzHL1BtfGK+mAK11R3rzP4O2FmvuZQ52ad48r6z35OqcYI
BlrBHQuKcafzqSFJEIw6a1rORI51k21J4ooKdXwBoiQjYrjbGw2cV63T898Cs6+g
SVXm74dEqudA49V5VsrmgY/FP7HHHB/1Bghl9laHvP7zFyJvJ0Koxg/Hgnp4D8rE
f0nz2ZSmZsyQZI+p2hfP6te/osn11846NajnVyXqTj8azalMTn56/THidt+/0yVP
MGxeTsy12pasp2D7+tnVfQAzc0uc1QUZo67xrWWx+W62iVvUZ0l5ps/CWEbGf/vj
rUqnqxdtAgMBAAECggEATa4vufRDr3tdE/Vn69Vcgsyr9N5n/2x0KXZaZ0jL09dt
GfB40nC/SGrBo0FKk2Mkh8f4yBrSuPI+dP5ieZ741IVGiaGLYlmVzKZpIBMUyMhr
qObDxe5SoLFyCO3CxsJc5QEMIKkd6e4A+vTrCe8+x02CkkIekyoLUE17q3eBGdbm
IaSTqToNS+JpwhSwWkHK0NMzk+cTu97rxcfCPY7zjB3E4URCm345D8RHYuF+fKfH
21iXkk/JJ06OrGC6WxHhtVe5ipLKUkGUPpAi83a3GHR8RX7Xo/BVzXVqyWQfs/3l
W3/jPrFz2rQAo24NQOvW3QRHoCGaCk0v7/ix6zSI5wKBgQDb28lpgDONys9uX4AK
NOvkyT7mGry+0Laopu8oVx4thi60ixOdDnj5UUMPdmw7oabmzoXzQX+xEFb21/L1
USfu2cPIZJoWZQwzQ1reJdoNlFYWTKCDsZ1ewUtiiY9YerFe7dplu8mx9OJz6UgI
Iem0maAiIXjVuRFI2WrOLH2ONwKBgQDG+8KFAegTIqrFklygSDQUM4/oSG9oTHjk
nZCcwr44dYkQjG2ab+PRsRu48gwsu6TcyiLg1aFkSFzhrKOjoksdYAOmGRKT1bkT
abeuQFj8PJplNjKe3JpDgwUwwwfxMLVa1qDgwxOO2ss8KumTDmH24ifwA0PAD30S
8L/NzhzVewKBgQDDE8MkrzIPL9LZ1jbf15EvUTlUJokurjZEcspletc2AYdTfsuK
YZsJbQcZozPpvUdAKCNGcemBDER9haL39GiAUAydtFXcZRBsM+lejytpPSMdaiRK
dcByoQPojLH6CfNzjYxpFDSkh+qeenEFdkHNkzo4zoZO7ITfpLpV8bf+BQKBgQDF
oqc4voUIBIXDiLTRSgXYDsKfftqQqGvm/gz1doPCxhLmSeRO4VVRH+0eC+NFxvZf
ibGMyQLs/OasC3HZBEhBG+1j20FVbpDNlFIpE6aOmY7seiFbIldiOM89+Gh6CdwH
JmhR9dAGMpBr6V9nfYCV21i5jjSLnZan6lFHk7GV4wKBgQDBVpXL+02lg7iILL6I
reAM91IO/qRD8J2Jz6YkOt3xXLWzI6lnklWTS0nNqqNu14dZ/W8X+N1yrb9rX/YD
8qOiAVLmgff95WzXgWPLLDffiIs/7rsVTdYJCEhyYeA4JJATg9y2pEgjDvUKTBI5
erLQmtnKSEkes6jFEhOqRYgcQg==
-----END PRIVATE KEY-----
"#;
let cert = r#"-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIUTd/Zr8U53zdbEz9RMV2OD3FdxVowDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVQxDTALBgNVBAoMBFRlc3QxFjAUBgNVBAsMDVRlc3Rj
b250YWluZXIxDzANBgNVBAMMBnJvb3RDQTAeFw0yNDA5MjQwNjQzMzlaFw0yNTA5
MjQwNjQzMzlaME8xCzAJBgNVBAYTAkFUMQ0wCwYDVQQKDARUZXN0MRYwFAYDVQQL
DA1UZXN0Y29udGFpbmVyMRkwFwYDVQQDDBBsZGFwLmV4YW1wbGUub3JnMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquQ1VN0NVJSVbrMGKvOzm0dr5fuD
XFXGcxy9QbXxivpgCtdUd68z+DthZr7mUOdmnePK+s9+TqnGCAZawR0LinGn86kh
SRCMOmtazkSOdZNtSeKKCnV8AaIkI2K42xsNnFet0/PfArOvoElV5u+HRKrnQOPV
eVbK5oGPxT+xxxwf9QYIZfZWh7z+8xcibydCqMYPx4J6eA/KxH9J89mUpmbMkGSP
qdoXz+rXv6LJ9dfOOjWo51cl6k4/Gs2pTE5+ev0x4nbfv9MlTzBsXk7MtdqWrKdg
+/rZ1X0AM3NLnNUFGaOu8a1lsflutolb1GdJeabPwlhGxn/7461Kp6sXbQIDAQAB
o4GIMIGFMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAeBgNV
HREEFzAVgglsb2NhbGhvc3SCCG9wZW5sZGFwMB0GA1UdDgQWBBSmpP49h2VxAv31
Cfa2awq0I8tKbTAfBgNVHSMEGDAWgBTwYJpbDj3GrICSwAQj4TbBMXp46DANBgkq
hkiG9w0BAQsFAAOCAQEAU0h83lpOFCbBfRQa7w7hZ+FJSXUZ4VX0tWFqmNx/IpxF
7Cxi/LUKg5Gpy9T45rBFJetDqWlPjR+WDMEPO1g7yb5//xawenM9SueuV0B86qyo
CmJtfAaBeEVQRZn503GJZMJzfVTRYuGQPmSF41M5O81t0Mu6Bw3qCNQgIJ5WANkm
3XIT0B77G/Y+NvTlXZEKdQ6xqbepvpUubayLR8eix7aIwT4C++dpfheQGcPVJu0j
RAK7To6s2WXfbrWo547RYux1fQrlIwF0aGbkFaHb6d2X0ifOeYAZvks7QIXyc2c0
f8GCAzsWx/hkplHugMaa+28Tz3jVj4BXzqWas4/gcA==
-----END CERTIFICATE-----
"#;

let _ = pretty_env_logger::try_init();
let openldap_image = OpenLDAP::default()
.with_allow_anon_binding(false)
.with_user("maximiliane", "pwd1")
.with_certs(
cert.to_string().into_bytes(),
key.to_string().into_bytes(),
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 af983e0

Please # to comment.