Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions sssd_test_framework/hosts/ad.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,43 @@ def naming_context(self) -> str:

return self.__naming_context

def export_root_ca_certificate(self) -> str:
"""
Export the AD root CA certificate in PEM format.

This method retrieves the most recent root CA certificate from the AD
domain controller's local machine root certificate store and exports it
in PEM format. The certificate is used for LDAPS (LDAP over SSL/TLS)
connections.

:return: PEM-formatted root CA certificate content.
:rtype: str
:raises RuntimeError: If certificate cannot be exported.
"""
result = self.conn.run(
"""
$cert = Get-ChildItem Cert:\\LocalMachine\\Root |
Sort-Object NotBefore -Descending |
Select-Object -First 1

if (-not $cert) {
throw "No root CA certificate found"
}

$pem = "-----BEGIN CERTIFICATE-----`r`n" +
[Convert]::ToBase64String($cert.RawData, "InsertLineBreaks") +
"`r`n-----END CERTIFICATE-----"

Write-Output $pem
""",
raise_on_error=False,
)

if result.rc != 0:
raise RuntimeError(f"Failed to export root CA certificate: {result.stderr}")

return result.stdout.strip()

def disconnect(self) -> None:
return

Expand Down
6 changes: 6 additions & 0 deletions sssd_test_framework/roles/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ..utils.sss_override import SSSOverrideUtils
from ..utils.sssctl import SSSCTLUtils
from ..utils.sssd import SSSDUtils
from ..utils.tls import TLSUtils
from ..utils.vfido import Vfido
from .base import BaseLinuxRole

Expand Down Expand Up @@ -118,6 +119,11 @@ def __init__(self, *args, **kwargs) -> None:
Managing virtual passkey device and service
"""

self.tls: TLSUtils = TLSUtils(self.host)
"""
TLS and certificate management.
"""

def setup(self) -> None:
"""
Called before execution of each test.
Expand Down
123 changes: 123 additions & 0 deletions sssd_test_framework/utils/tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""TLS and certificate management utilities."""

from __future__ import annotations

from pytest_mh import MultihostHost, MultihostUtility
from pytest_mh.conn import ProcessResult

__all__ = [
"TLSUtils",
]


class TLSUtils(MultihostUtility[MultihostHost]):
"""
Interface for TLS/SSL certificate operations.

.. code-block:: python
:caption: Example usage

@pytest.mark.topology(KnownTopology.AD)
def test_ldaps(client: Client, ad: AD):
# Export and trust AD's root CA certificate
ca_cert = ad.host.export_root_ca_certificate()
client.tls.trust_ca_certificate(ca_cert, "ad-root-ca")

# Now LDAPS operations work
r = client.adcli.join(
domain=ad.host.domain,
login_user=ad.host.adminuser,
password=ad.host.adminpw,
args=["--use-ldaps"]
)
assert r.rc == 0
"""

def trust_ca_certificate(
self,
certificate_content: str,
certificate_name: str | None = None,
) -> ProcessResult:
"""
Trust a CA certificate by installing it to system trust store.

:param certificate_content: PEM-formatted certificate content.
:type certificate_content: str
:param certificate_name: Optional certificate filename (without extension).
:type certificate_name: str | None
:return: Result of update-ca-trust command.
:rtype: ProcessResult
"""
if certificate_name is None:
certificate_name = "custom-ca"

cert_path = f"/etc/pki/ca-trust/source/anchors/{certificate_name}.crt"

# Write certificate to trust anchors
self.host.conn.run(
f"cat > {cert_path}",
Comment thread
shridhargadekar marked this conversation as resolved.
Outdated
input=certificate_content,
)

# Update system trust store
return self.host.conn.run("update-ca-trust")
Comment thread
shridhargadekar marked this conversation as resolved.
Outdated

def trust_ca_certificate_file(
self,
certificate_path: str,
certificate_name: str | None = None,
) -> ProcessResult:
"""
Trust a CA certificate from file path.

:param certificate_path: Path to certificate file on local machine.
:type certificate_path: str
:param certificate_name: Optional certificate filename (without extension).
:type certificate_name: str | None
:return: Result of update-ca-trust command.
:rtype: ProcessResult
"""
with open(certificate_path) as f:
cert_content = f.read()

return self.trust_ca_certificate(cert_content, certificate_name)

def configure_ldap_tls(
self,
*,
tls_reqcert: str = "demand",
tls_cacertdir: str | None = None,
tls_cacert: str | None = None,
) -> None:
"""
Configure LDAP client TLS settings in /etc/openldap/ldap.conf.

:param tls_reqcert: Certificate verification level (never, allow, try, demand, hard).
:type tls_reqcert: str
:param tls_cacertdir: Path to CA certificate directory.
:type tls_cacertdir: str | None
:param tls_cacert: Path to specific CA certificate file.
:type tls_cacert: str | None
"""
config_lines = [f"TLS_REQCERT {tls_reqcert}"]

if tls_cacertdir:
config_lines.append(f"TLS_CACERTDIR {tls_cacertdir}")

if tls_cacert:
config_lines.append(f"TLS_CACERT {tls_cacert}")

config_content = "\n".join(config_lines) + "\n"
self.host.conn.run(
"cat > /etc/openldap/ldap.conf",
input=config_content,
)
Comment thread
shridhargadekar marked this conversation as resolved.

def disable_certificate_verification(self) -> None:
"""
Disable TLS certificate verification (for testing only).

.. warning::
This is insecure and should only be used for development/testing.
"""
self.configure_ldap_tls(tls_reqcert="never")
Loading