Skip to main content
Glama
Arize-ai

@arizeai/phoenix-mcp

Official
by Arize-ai
LDAP-TLS-TESTING.md11.5 kB
# LDAP TLS Security Testing ## Overview Comprehensive automated testing suite that validates LDAP TLS implementations using an **adversarial MITM proxy**. The proxy actively attempts to steal credentials from LDAP traffic. Success = vulnerability, failure = secure. ## Architecture ```mermaid graph TB subgraph "Test Orchestration" TestRunner[ldap-test<br/>test_ldap_tls.py] end subgraph "Applications Under Test" PhoenixSTARTTLS[Phoenix STARTTLS<br/>:6007] PhoenixLDAPS[Phoenix LDAPS<br/>:6008] Grafana[Grafana LDAP<br/>:3000] end subgraph "Security Layer" MITM[MITM Proxy<br/>:3389<br/>Credential Extractor] end subgraph "Backend" LDAP[OpenLDAP Server<br/>:389 STARTTLS<br/>:636 LDAPS] end TestRunner -->|Test Auth| PhoenixSTARTTLS TestRunner -->|Test Auth| PhoenixLDAPS TestRunner -->|Test Auth| Grafana TestRunner -->|Parse Logs| MITM PhoenixSTARTTLS -->|via Proxy| MITM Grafana -->|via Proxy| MITM PhoenixLDAPS -->|Direct| LDAP MITM -->|Forward + Inspect| LDAP ``` ## Files ``` scripts/docker/devops/ ├── scripts/ │ ├── ldap_mitm_proxy.py # Adversarial MITM proxy (credential extraction) │ └── test_ldap_tls.py # Test orchestration (all tests here) ├── overrides/ │ └── ldap-test.yml # Docker Compose config (all services) ├── ldap-seed.ldif # Test data (11 users with edge cases) └── ldap-grafana.toml # Grafana LDAP config (for comparison) ``` ## Quick Start ```bash # Start all services and run tests cd scripts/docker/devops COMPOSE_PROFILES=ldap-test docker compose \ -f docker-compose.yml \ -f overrides/ldap-test.yml \ up -d --build # View test results docker logs devops-ldap-test # View MITM proxy analysis docker logs devops-ldap-mitm-proxy # Parse structured logs docker logs devops-ldap-mitm-proxy 2>&1 | grep "^{" | jq # Stop everything COMPOSE_PROFILES=ldap-test docker compose \ -f docker-compose.yml \ -f overrides/ldap-test.yml \ down ``` ## Services | Service | Purpose | Port | |---------|---------|------| | `ldap` | OpenLDAP server with TLS | 389, 636 | | `ldap-mitm-proxy` | Adversarial credential extractor + HTTP API | 3389 (proxy), 8080 (API) | | `phoenix-starttls` | Phoenix with STARTTLS mode | 6007 | | `phoenix` | Phoenix with LDAPS mode | 6006 | | `grafana-ldap` | Grafana for comparison | 3000 | | `ldap-test` | Test runner (executes test_ldap_tls.py) | - | ## Test Execution Flow ```mermaid sequenceDiagram participant T as Test Runner participant P as Phoenix/Grafana participant M as MITM Proxy participant L as LDAP Server Note over T: Phase 1: Baseline Tests T->>L: Test plaintext connection L-->>T: OK (baseline) T->>L: Test STARTTLS (correct) L-->>T: OK (encrypted) T->>L: Test LDAPS L-->>T: OK (encrypted) Note over T: Phase 2: Application Tests T->>P: POST /auth/ldap/login P->>M: LDAP Bind Request alt TLS Working (Phoenix) M->>M: Try to extract credentials Note over M: Gets encrypted data ✅ M->>L: Forward encrypted request L-->>M: Success M-->>P: Success else TLS Broken (Grafana) M->>M: Parse LDAP protocol Note over M: Extracts plaintext password 🚨 M->>L: Forward plaintext request L-->>M: Success M-->>P: Success end P-->>T: HTTP 200/204 Note over T: Phase 3: Adversarial Analysis T->>M: Parse logs (docker logs) M-->>T: Stolen credentials (if any) T->>L: Verify stolen credentials L-->>T: Auth success/failure T->>T: Generate final verdict ``` ## Test Phases ```mermaid graph LR subgraph "Phase 1: Baseline" B1[Plaintext LDAP] B2[STARTTLS Correct] B3[LDAPS] end subgraph "Phase 2: Applications" A1[Phoenix STARTTLS] A2[Phoenix LDAPS] A3[Grafana STARTTLS] end subgraph "Phase 3: Adversarial" V1[Parse MITM Logs] V2[Verify Credentials] V3[Final Verdict] end B1 --> B2 --> B3 --> A1 A1 --> A2 --> A3 --> V1 V1 --> V2 --> V3 ``` ### Phase 1: Baseline LDAP Connectivity Verify LDAP server works with different TLS modes: - **Plaintext** (port 389, no TLS) - Baseline - **STARTTLS** (port 389 with upgrade) - Correct implementation - **LDAPS** (port 636, TLS from start) - Direct TLS ### Phase 2: Application Security Tests Test Phoenix and Grafana via MITM proxy: - **Phoenix STARTTLS** → Routes through proxy → Should be SECURE - **Phoenix LDAPS** → Direct connection → Should be SECURE - **Grafana STARTTLS** → Routes through proxy → Currently VULNERABLE ### Phase 3: Adversarial Analysis - Parse MITM proxy logs for extracted credentials - Verify extracted credentials actually work - Determine security verdict ## Security Validation Model ```mermaid sequenceDiagram participant Client as Application<br/>(Phoenix/Grafana) participant MITM as MITM Proxy<br/>(Adversary) participant LDAP as LDAP Server Client->>MITM: Login Request rect rgb(255, 240, 240) Note over MITM: Adversarial Attack:<br/>Parse LDAP Bind Request alt Plaintext Credentials (VULNERABLE) MITM->>MITM: Extract DN + Password Note over MITM: 🚨 SUCCESS<br/>Credentials Stolen<br/>(TLS Failed) else Encrypted Data (SECURE) MITM->>MITM: Attempt Extraction Note over MITM: ✅ FAILURE<br/>Only Encrypted Bytes<br/>(TLS Working) end end MITM->>LDAP: Forward Request LDAP-->>MITM: Response MITM-->>Client: Response Note over MITM: Log Verdict:<br/>SECURE or VULNERABLE ``` ## Security Decision Flow ```mermaid flowchart TD Start([LDAP Bind Request]) --> MITM{MITM Proxy<br/>Intercepts} MITM --> Parse[Parse LDAP Protocol<br/>ASN.1/BER Structure] Parse --> Extract{Can Extract<br/>Credentials?} Extract -->|Yes| Stolen[Credentials Extracted] Extract -->|No| Encrypted[Only Encrypted Data] Stolen --> Verify{Verify Against<br/>LDAP Server} Verify -->|Works| Vuln[🚨 VULNERABLE<br/>TLS Failed] Verify -->|Fails| FalsePos[False Positive<br/>Parse Error] Encrypted --> Secure[✅ SECURE<br/>TLS Working] Vuln --> Report[Log Security Event<br/>credentials_stolen] FalsePos --> Report Secure --> Report Report --> End([Exit: 0=Pass, 1=Fail]) ``` ## Expected Results ### Phoenix (SECURE) ``` Application: phoenix-starttls StartTLS requested: True TLS handshake detected: True Credentials extracted: 0 Verdict: SECURE ✅ ``` ### Grafana (VULNERABLE) ``` Application: grafana-ldap StartTLS requested: False TLS handshake detected: False Credentials extracted: 2 • cn=readonly,dc=example,dc=com → readonly_password • uid=alice,ou=users,dc=example,dc=com → password123 Verdict: VULNERABLE 🚨 ``` ## Structured Logging All security events are logged as JSON for programmatic analysis: ```json { "timestamp": 1764371725.425, "event": "credentials_stolen", "connection_id": 3, "application": "grafana-ldap", "client_ip": "172.18.0.13", "bind_dn": "cn=readonly,dc=example,dc=com", "password": "readonly_password", "password_length": 17, "direction": "client→server" } ``` ### MITM HTTP API The proxy now exposes a lightweight read-only API that streams the same structured events without requiring Docker access: ```bash # Fetch all recorded events (Phoenix STARTTLS, Grafana, etc.) curl -s http://localhost:8080/events | jq # Fetch only events newer than a UNIX timestamp curl -s "http://localhost:8080/events?since=$(date +%s)" | jq ``` - Endpoint: `GET /events` → `{ "events": [...] }` - Optional `since` query string filters by `timestamp`. - Health check: `GET /healthz` → `{ "status": "ok" }` `test_ldap_tls.py` consumes this API (via `MITM_API_URL`, default `http://ldap-mitm-proxy:8080`) during Phase 3 so the runner can analyze adversarial results from inside its container. ### Event Types ```mermaid stateDiagram-v2 [*] --> connection_established: New Connection connection_established --> starttls_requested: Client Requests StartTLS connection_established --> tls_handshake_detected: LDAPS Mode starttls_requested --> tls_handshake_detected: TLS Negotiation connection_established --> credentials_stolen: Plaintext Bind (VULNERABLE) starttls_requested --> credentials_stolen: No TLS Upgrade (VULNERABLE) tls_handshake_detected --> connection_closed: Encrypted (SECURE) credentials_stolen --> connection_closed: Plaintext (VULNERABLE) connection_established --> connection_closed: No Bind connection_closed --> [*] note right of credentials_stolen Security Event! Password extracted end note ``` Event descriptions: - `connection_established` - New client connection - `starttls_requested` - Client requested StartTLS upgrade - `tls_handshake_detected` - TLS negotiation started - `credentials_stolen` - Plaintext credentials extracted (vulnerability) - `connection_closed` - Connection finished with verdict ## Exit Codes - **0** - All tests passed, no vulnerabilities detected - **1** - Test failures or security vulnerabilities found ## Test Data `ldap-seed.ldif` contains 11 test users covering edge cases. ## Troubleshooting ### Tests fail with connection errors ```bash # Check if services are running docker ps | grep devops # Restart services COMPOSE_PROFILES=ldap-test docker compose \ -f docker-compose.yml \ -f overrides/ldap-test.yml \ restart ``` ### MITM proxy shows no connections ```bash # Check if apps are configured to use proxy docker exec devops-phoenix-starttls env | grep LDAP_HOST # Should show: PHOENIX_LDAP_HOST=ldap-mitm-proxy ``` ### Want to test manually ```bash # Test Phoenix STARTTLS curl -X POST http://localhost:6007/auth/ldap/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"password123"}' # Check MITM logs docker logs devops-ldap-mitm-proxy 2>&1 | tail -30 ``` ## Reproducibility Checklist - ✅ Single entry point: `test_ldap_tls.py` - ✅ No manual steps required - ✅ All dependencies in Docker - ✅ Deterministic test data - ✅ Idempotent execution - ✅ Clear exit codes (0 = pass, 1 = fail) - ✅ Structured logs for automation - ✅ Self-documented via logging output ## Future Testing To add new tests: ```mermaid flowchart LR A[Add Test Method] --> B[Add to Test Phase] B --> C[Document Behavior] C --> D[Write Failing Test] D --> E[Fix Implementation] E --> F[Verify MITM Detection] F --> G[Update README] ``` 1. Add test method to `LDAPTLSSecurityTester` class 2. Add to appropriate test phase in `run_all_tests()` 3. Document expected behavior in this README 4. Test fails first, then fix implementation 5. Verify MITM proxy detects the issue 6. Update this README with new test documentation Example: ```python def test_new_feature(self) -> TestResult: """Test description here.""" return self._test_http_login( name="New Feature Test", url=f"{self.config.phoenix_url}/auth/new", payload={"user": "test"}, expected_status=200, description="New feature works", ) ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Arize-ai/phoenix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server