Files
kevinveenbirkenbachandClaude Opus 5.5 d89c74feab feat(security): harden PHP services and proxy response headers
Proxy:
- server_tokens off, X-Powered-By stripped from upstream responses,
  HSTS max-age raised to one year.
- No global X-Content-Type-Options: under nosniff, ORB (step 3.4)
  blocks no-cors cross-origin responses typed text/html, e.g. tracking
  beacons answered with an empty 204.

PHP (CVE-2026-109618, CVE-2026-109619):
- New role sys-svc-compose-php installs a shared ini (expose_php Off,
  session.upload_progress.enabled 0, fatal_error_backtraces 0 for
  PHP 8.5, interim memory_limit -1) and a PHP-FPM [www] drop-in
  (pm.max_requests = PHP_FPM_MAX_REQUESTS) once per stack host,
  before 04_files renders any compose file.
- The host file names carry a sha1 prefix of the controller-side
  source, so a content change alters every PHP app's compose.yml and
  recreates it, also on remote deploys where compose_volumes cannot
  read the stack host file and hashes the path instead.
- 20 PHP roles mount the ini as a compose/swarm config into their
  image's scan dir; moodle, nextcloud and shopware also mount the FPM
  drop-in. Magento keeps its own pool: its php-fpm.conf has no
  include= and is rendered without notify.
- Joomla's php-upload.ini now loads as zzz-joomla-uploads.ini so its
  own memory_limit wins: the media API compares Content-Length with
  toBytes(memory_limit) and rejects every upload at -1.

TODOs record the memory_limit revert condition and accepted
trade-offs, Akaunting on PHP 8.1, FusionDirectory on PHP 7.4 and the
public Keycloak admin console.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 21:03:38 +02:00
..

SuiteCRM

Description

Manage your customer relationships with SuiteCRM, a powerful open-source CRM platform extending SugarCRM with advanced modules, workflows, and integrations. This role integrates SuiteCRM into the Infinito.Nexus ecosystem with centralized database, mail and LDAP-ready single sign-on integration. 🚀💼

Overview

This Ansible role deploys SuiteCRM using Docker and the Infinito.Nexus shared stack. It handles:

  • MariaDB database provisioning via the sys-svc-rdbms role
  • NGINX domain and reverse-proxy configuration
  • Environment variable management through Jinja2 templates
  • Docker Compose orchestration for the SuiteCRM application container
  • Native LDAP authentication via Symfony’s LDAP configuration
  • Declarative SAML SSO against Keycloak (AUTH_TYPE=saml, wired entirely from env vars — no admin-panel step)

With this role, you get a production-ready CRM environment that plugs into your existing IAM stack.

Cosmos

The diagram places SuiteCRM in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).

flowchart LR
    subgraph deps [Dependencies]
        dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
        dep_svc_db_mariadb["svc-db-mariadb 🐳🐝"]
        dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
        dep_svc_db_redis["svc-db-redis 🐳🐝"]
        dep_svc_net_tor["svc-net-tor 🐳🐝"]
        dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
        dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
        dep_web_app_matomo["web-app-matomo 🐳🐝"]
        dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
        dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
        dep_web_svc_css["web-svc-css 💻"]
        dep_web_svc_logout["web-svc-logout 🐳🐝"]
    end
    subgraph role [web-app-suitecrm 🐳🐝]
        svc_sso["sso"]
        svc_logout["logout"]
        svc_ldap["ldap"]
        svc_dashboard["dashboard"]
        svc_matomo["matomo"]
        svc_email["email"]
        svc_mariadb["mariadb"]
        svc_redis["redis"]
        svc_suitecrm["suitecrm"]
        svc_css["css"]
        svc_prometheus["prometheus"]
        svc_tor["tor"]
        svc_container_backup["container_backup"]
    end
    dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
    dep_svc_db_mariadb -. "0..1" .-> svc_mariadb
    dep_svc_db_openldap -. "0..1" .-> svc_ldap
    dep_svc_db_redis -. "0..1" .-> svc_redis
    dep_svc_net_tor -. "0..1" .-> svc_tor
    dep_web_app_dashboard -. "0..1" .-> svc_dashboard
    dep_web_app_keycloak -. "0..1" .-> svc_sso
    dep_web_app_matomo -. "0..1" .-> svc_matomo
    dep_web_app_prometheus -. "0..1" .-> svc_prometheus
    dep_web_app_stalwart -. "0..1" .-> svc_email
    dep_web_svc_css -. "0..1" .-> svc_css
    dep_web_svc_logout -. "0..1" .-> svc_logout

Solid 1:1 edges are fixed relationships; dashed 0..1 edges are conditional (enabled only in matching deployments); red 0..0 edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in meta/main.yml.

Features

  • Sales & Service CRM: Accounts, Contacts, Leads, Opportunities, Cases, Campaigns and more 📊
  • Workflow Engine: Automate business processes and notifications 🛠️
  • LDAP Authentication: Centralize user authentication against OpenLDAP 🔐
  • SSO-Ready: SuiteCRM's own SAML login against Keycloak, configured declaratively from the rendered env 🌐
  • Config via Templates: Fully customizable .env and compose.yml rendered via Jinja2 ⚙️
  • Health Checks & Logging: Integrates with Infinito.Nexus health checking and journald logging 📈
  • Modular Role Composition: Uses shared roles for DB, proxy and monitoring to keep your stack consistent 🔄

Quick Setup

Development

Clone, set up the workstation, and deploy SuiteCRM onto the local stack:

git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-suitecrm full_cycle=false

Production

Run the published image to provision the inventory and deploy SuiteCRM to a managed server (the mounted volume persists the inventory):

APP=web-app-suitecrm
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"

docker run --rm -it \
  -v "$PWD/inventories:/etc/infinito.nexus/inventories" \
  -e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
  ghcr.io/infinito-nexus/core/debian bash -c '
    INVENTORY=/etc/infinito.nexus/inventories/production
    infinito administration inventory provision "$INVENTORY" \
      --inventory-file "$INVENTORY/devices.yml" \
      --host "$HOST" \
      --include "$APP" \
      --vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
    infinito administration deploy dedicated "$INVENTORY/devices.yml" \
      --password-file "$INVENTORY/.password" \
      --diff -vv'

Further Resources

LDAP & SSO Notes

  • LDAP is configured via environment variables (AUTH_TYPE=ldap, LDAP_*) in templates/env.j2. The role additionally renders templates/ldap.yaml.j2 into the container's extensions/<software>/config/services/ldap/, which maps the LDAP attributes onto SuiteCRM's user fields for auto-created accounts.

  • SSO is SuiteCRM's own SAML login against Keycloak (sso.flavor: saml in meta/services.yml), not a proxy in front of it. Unauthenticated requests hit the Symfony firewall's SAML entry point, which redirects to Keycloak; the assertion comes back to /saml/acs and establishes a real SuiteCRM session, so the app renders its own authenticated UI including its logout control. Accounts are created on first login from the assertion (SAML_AUTO_CREATE), and the IdP signing certificate is read from Keycloak's realm descriptor by files/shell/docker-entrypoint.sh at boot, over the Tor SOCKS proxy whenever the issuer is an onion, because a .onion issuer is unresolvable directly. /auth stays reachable as the local break-glass login.

  • Auto-created accounts reach User::save() through SuiteCRM's own CreateUser while the request is still anonymous. Since 8.10.2 that save answers RuntimeException: Not authorized and forces status = Inactive, so files/patch/external-auth-user-create.patch makes the stored system user the current user around the save and restores the caller's afterwards. files/Dockerfile applies it with patch -F0 on the unpacked release; the image build fails once the hunk stops matching.

Persona contract opt-outs

templates/env.j2 seeds only the administrator into SuiteCRM (SUITECRM_ADMIN_USERNAME / SUITECRM_ADMIN_PASSWORD); biber reaches the app through SuiteCRM's own SAML login against Keycloak, where SAML_AUTO_CREATE mints the account on first assertion. In the services.sso.enabled: false matrix variants there is no SAML entry point and biber has no native account, so templates/playwright.env.j2 renders PERSONA_BIBER_BLOCKED=true for the generic persona journeys (the LDAP spec still signs biber in from the directory). The administrator persona keeps its native login via ADMIN_NATIVE_PASSWORD and runs in every variant.

Credits

Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).