Files
cacti/tests/e2e/docker-compose.yml
Thomas VincentandTheWitness 055c5ef5fa security: reject LDAP logins that skip a domain bind (#8009)
* hardening: validate the login realm and escape the LDAP username per context

domains_login_process() now rejects a realm get_auth_realms() does not list and
fails closed on one that is listed but has no domain behind it, and the Ldap
class escapes the username for the DN and the search filter separately rather
than relying on a filter-only blocklist.

- an unlisted realm sent with a non-empty password used to match no branch at
  all and returned an empty user with no error, which auth_login.php reads as
  an authenticated user and provisions from the template account

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* security: reject LDAP logins that skip a domain bind

Posted realms outside user_domains no longer fall through to template or guest login.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(ldap): align escaping contract with username lookup

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: cover LDAP domain login through OpenLDAP

The e2e stack now binds a real directory user and checks that a forged realm cannot skip that bind.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(ldap): address Copilot review feedback

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: substitute LDAP filter placeholders in one pass

LDAP_ESCAPE_FILTER leaves angle brackets intact, so a looped str_replace can rescan one value as another key.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* Change realm retrieval function from gnrv to gfrv

* tests: add gfrv() stub to harnesses that exercise gfrv()-calling code

domains_login_process() and api_aggregate_convert_template() call gfrv() for
realm/aggregate_template_id, but their test harnesses only stubbed gnrv(),
so Pest failed with 'Call to undefined function gfrv()'.

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-09-12 20:49:55 -04:00

73 lines
2.5 KiB
YAML

#
# +-------------------------------------------------------------------------+
# | Copyright (C) 2004-2026 The Cacti Group |
# | |
# | This program is free software; you can redistribute it and/or |
# | modify it under the terms of the GNU General Public License |
# | as published by the Free Software Foundation; either version 2 |
# | of the License, or (at your option) any later version. |
# +-------------------------------------------------------------------------+
# | Cacti: The Complete RRDtool-based Graphing Solution |
# +-------------------------------------------------------------------------+
# | http://www.cacti.net/ |
# +-------------------------------------------------------------------------+
#
services:
openldap:
image: osixia/openldap:1.5.0
command: --copy-service
environment:
LDAP_ORGANISATION: Cacti E2E
LDAP_DOMAIN: example.org
LDAP_ADMIN_PASSWORD: adminpassword
LDAP_TLS: "false"
volumes:
- ./ldap/50-e2e.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-e2e.ldif:ro
healthcheck:
test: ["CMD", "ldapsearch", "-x", "-H", "ldap://127.0.0.1:389", "-b", "dc=example,dc=org", "-D", "cn=admin,dc=example,dc=org", "-w", "adminpassword", "-LLL", "cn=ldapuser"]
interval: 5s
timeout: 5s
retries: 20
start_period: 15s
mariadb:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: cactiroot
MYSQL_DATABASE: cacti
MYSQL_USER: cactiuser
MYSQL_PASSWORD: cactiuser
command: >
--max-allowed-packet=500M
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-pcactiroot"]
interval: 5s
timeout: 3s
retries: 20
tmpfs:
- /var/lib/mysql
cacti:
build:
context: ../..
dockerfile: tests/e2e/Dockerfile
depends_on:
mariadb:
condition: service_healthy
openldap:
condition: service_healthy
environment:
CACTI_DB_HOST: mariadb
CACTI_DB_NAME: cacti
CACTI_DB_USER: cactiuser
CACTI_DB_PASS: cactiuser
CACTI_DB_ROOT_PASS: cactiroot
ports:
- "${HOST_PORT:-8080}:80"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1/"]
interval: 5s
timeout: 3s
retries: 30