Demo Environment: This repository contains demo credentials and self-signed certificates for testing only. Do not use in production without proper security configuration.
- Passbolt Pro with OIDC SSO integration (Keycloak over HTTPS)
- Multi-directory LDAP synchronization (aggregation and direct approaches)
- LDAPS (implicit TLS) for secure LDAP connections
- SMTPS for secure email communication
- Valkey session handling
- Certificate automation for development and testing
- SCIM API testing with Bruno
- Docker and Docker Compose
- Passbolt Pro subscription key
- macOS or Linux
- Quick Start
- LDAP Integration
- Traefik Reverse Proxy (Default)
- Services Overview
- Valkey Session Handling
- Environment Variables Configuration
- SIEM Audit Logging
- Keycloak SSO Configuration
- SMTP Configuration
- GPG Primer
- User and Group Management
- Testing and Verification
- Troubleshooting
Choose your LDAP integration approach:
Default (Traefik + LDAP Aggregation):
./scripts/setup.shFull stack with Traefik reverse proxy, LDAP aggregation via OpenLDAP meta backend.
LDAP Aggregation with Nginx:
./scripts/setup-aggregation-demo.shOpenLDAP meta backend with Nginx instead of Traefik. Uses direct port access.
Direct Multi-Domain:
./scripts/setup-dual-ldap.shPassbolt connects directly to multiple LDAP servers via PHP configuration.
Single LDAP with Nginx:
./scripts/setup-nginx-single.shBasic single directory setup with Nginx.
All scripts set up LDAP directories, generate GPG keys, and create Passbolt admin user 'ada'. See LDAP Integration for detailed comparison.
-
Add host entries:
echo "127.0.0.1 passbolt.local keycloak.local smtp.local traefik.local ldap1.local ldap2.local ldap-meta.local" | sudo tee -a /etc/hosts
-
Add Passbolt Pro subscription key:
cp /path/to/your/subscription_key.txt ./subscription_key.txt
-
Generate TLS certificates:
./scripts/generate-certificates.sh
-
Make scripts executable:
chmod +x scripts/ldap/*/*.sh scripts/tests/*/*.sh
-
Start the environment:
docker compose up -d
-
Setup LDAP test data:
./scripts/ldap/setup/initial-setup.sh
-
Create the default admin user (optional - now automatic in setup.sh):
./scripts/ldap/setup/create-admin.sh
-
Configure LDAP in Passbolt:
- Log in to Passbolt as administrator
- Go to Organization Settings > Users Directory
- Configure LDAP settings (see LDAP Integration)
Important Notes:
- LDAP users must be set up before creating the Passbolt admin user
- SMTP: Set "Use TLS" to No (SMTPS implicit TLS is used via
ssl://smtp.local) - Requires valid Passbolt Pro subscription key in
subscription_key.txt - Demo credentials are for testing only - use strong credentials in production
Two approaches for multi-directory LDAP integration for educational comparison.
| Aspect | Aggregation (Default) | Direct Multi-Domain |
|---|---|---|
| Architecture | Infrastructure proxy | Application-level |
| Configuration | Web UI or PHP file | Web UI or PHP file |
| LDAP Endpoint | Single unified (ldap-meta) | Multiple direct (ldap1, ldap2) |
| Setup Script | setup-aggregation-demo.sh |
setup-dual-ldap.sh |
LDAP1 (Passbolt Inc.) - dc=passbolt,dc=local:
ou=users: Ada, Betty, Carol, Dame, Edith
ou=groups: passbolt, developers, demoteam, admins
LDAP2 (Example Corp) - dc=example,dc=com:
ou=people: John, Sarah, Michael, Lisa
ou=teams: project-teams, security, operations, creative
Aggregation Unified View (dc=unified,dc=local):
dc=passbolt,dc=unified,dc=local → LDAP1
dc=example,dc=unified,dc=local → LDAP2
Passbolt connects to single meta backend. Configure via Web UI or use included PHP configuration (config/passbolt/ldap.php).
LDAP Meta Settings:
- Host:
ldap-meta.local - Port:
636(LDAPS) - Base DN:
dc=unified,dc=local - Username:
cn=readonly,dc=passbolt,dc=unified,dc=local(PHP config) orcn=admin,dc=unified,dc=local(Web UI) - Password:
readonly(PHP config) orsecret(Web UI) - Use SSL:
true
The meta backend transparently proxies to both LDAP1 and LDAP2.
Passbolt connects directly to both LDAP servers. Configure via Passbolt Web UI or use the included PHP configuration example (config/passbolt/ldap.php).
PHP Configuration Example:
The repository includes a ready-to-use multi-domain configuration at config/passbolt/ldap.php with two domains:
LDAP1 (Passbolt domain):
- Host:
ldap1.local - Port:
636(LDAPS) - Base DN:
dc=passbolt,dc=local - Username:
cn=readonly,dc=passbolt,dc=local - Password:
readonly - Paths:
ou=users,ou=groups
LDAP2 (Example domain):
- Host:
ldap2.local - Port:
636(LDAPS) - Base DN:
dc=example,dc=com - Username:
cn=reader,dc=example,dc=com - Password:
reader123 - Paths:
ou=people,ou=teams
Note: The PHP config is not used by default (Web UI configuration takes precedence). To use the PHP config, mount it into the Passbolt container.
Sync Command:
docker compose exec passbolt su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake directory_sync all --persist --quiet" www-dataAll LDAP connections use LDAPS (port 636) with SSL/TLS encryption.
Certificate Management:
- osixia/openldap auto-generates self-signed certificates
./scripts/fix-ldaps-certificates.shextracts certificates from containers- Certificates bundled into Passbolt container at build time
Test LDAPS:
docker compose exec passbolt openssl s_client -connect ldap:636 \
-servername ldap.local -CAfile /etc/ssl/certs/ldaps_bundle.crt -briefosixia/openldap Environment Variables:
LDAP_ORGANISATION: "Passbolt"
LDAP_DOMAIN: "passbolt.local"
LDAP_BASE_DN: "dc=passbolt,dc=local"
LDAP_ADMIN_PASSWORD: "P4ssb0lt"
LDAP_TLS: "true"
LDAP_READONLY_USER: "true"
LDAP_READONLY_USER_USERNAME: "readonly"
LDAP_READONLY_USER_PASSWORD: "readonly"Connection Methods:
- LDAPS (implicit TLS): Port 636 - Used by Passbolt
- STARTTLS: Port 389 - Alternative option
Certificate Location in Container:
/container/service/slapd/assets/certs/ldap.crt- Server certificate/container/service/slapd/assets/certs/ca.crt- CA certificate/container/service/slapd/assets/certs/ldap.key- Private key
Passbolt Web UI (Organization Settings > Directory):
- Users Path:
ou=users - Group Path:
ou=groups - User Filter:
(objectClass=inetOrgPerson) - Group Filter:
(objectClass=groupOfUniqueNames) - Username:
mail - Email:
mail - First Name:
givenName - Last Name:
sn
Sync Behavior: One-way read-only from LDAP to Passbolt. LDAP is the source of truth.
- Passbolt LDAP: https://www.passbolt.com/configure/ldap
- LdapRecord Multi-Domain: https://ldaprecord.com/docs/laravel/v2/configuration
- OpenLDAP Admin: https://www.openldap.org/doc/admin24/
Traefik provides automatic HTTPS routing and service discovery.
./scripts/setup.shUses the default docker-compose.yaml file.
YAML files (fixes indentation issues in Passbolt docs):
config/traefik/traefik.yaml- Main config with HTTP to HTTPS redirectconfig/traefik/conf.d/tls.yaml- TLS 1.2+ settingsconfig/traefik/conf.d/headers.yaml- Security headers
- Passbolt: https://passbolt.local
- Keycloak: https://keycloak.local
- SMTP4Dev: https://smtp.local
- Traefik Dashboard: https://traefik.local
./scripts/validate-traefik-config.sh # Checks YAML syntax, tabs, indentationdocker compose down
docker compose -f docker-compose.nginx.yaml up -d| Service | URL | Credentials | Purpose |
|---|---|---|---|
| Passbolt | https://passbolt.local | Created during setup | Main application |
| Keycloak | https://keycloak.local | admin / admin | SSO provider |
| SMTP4Dev | https://smtp.local | N/A | Email testing |
| Traefik | https://traefik.local | N/A | Reverse proxy dashboard |
| LDAP1 | ldap1.local:636 (LDAPS) | cn=readonly,dc=passbolt,dc=local / readonly | Passbolt Inc. directory |
| LDAP2 | ldap2.local:636 (LDAPS) | cn=reader,dc=example,dc=com / reader123 | Example Corp directory |
| LDAP Meta | ldap-meta.local:636 (LDAPS) | cn=admin,dc=unified,dc=local / secret | Aggregation proxy |
| Valkey | valkey:6379 (internal) | N/A | Session storage |
Valkey provides Redis-compatible session storage for better performance than file-based sessions.
Valkey service:
valkey:
image: valkey/valkey:9.0-trixie
ports: ["6379:6379"]
volumes: [valkey_data:/data]
command: valkey-server --appendonly yesPassbolt environment variables:
CACHE_CAKECORE_CLASSNAME: Cake\Cache\Engine\RedisEngine
CACHE_CAKECORE_HOST: valkey
CACHE_CAKECORE_PORT: 6379
CACHE_CAKECORE_PASSWORD: ""
CACHE_CAKECORE_DATABASE: 0
SESSION_DEFAULTS: cache# Test connectivity
docker compose exec valkey valkey-cli ping
# Check sessions
docker compose exec valkey valkey-cli keys "*session*"Environment variables documented in official Passbolt documentation:
APP_FULL_BASE_URL- Passbolt application URLDATASOURCES_DEFAULT_*- Database connection settings
EMAIL_TRANSPORT_DEFAULT_*- SMTP server settingsEMAIL_DEFAULT_FROM- Default sender email address
PASSBOLT_PLUGINS_DIRECTORY_SYNC_ENABLED- Enable Directory Sync plugin (Note: Currently not working, see task PB-45139 for fix)PASSBOLT_PLUGINS_SSO_ENABLED- Enable SSO pluginPASSBOLT_PLUGINS_SSO_PROVIDER_OAUTH2_ENABLED- Enable OAuth2 SSO providerPASSBOLT_SECURITY_SSO_SSL_VERIFY- SSO SSL verification
CACHE_CAKECORE_*- Valkey cache engine settingsSESSION_DEFAULTS- Session storage method
- Directory Sync details (host, port, credentials, filters) configured via Passbolt Web UI
- PHP TLS configuration in
config/php/ssl.ini
Passbolt supports file-based and syslog-based audit logging. Both can run simultaneously.
File Logging:
LOG_ACTION_LOGS_ON_FILE_ENABLED: "true"
LOG_ACTION_LOGS_ON_FILE_PATH: "/var/log/passbolt/" # trailing slash required on macOS
LOG_ACTION_LOGS_ON_FILE_FILE: "action-logs.log"
LOG_ACTION_LOGS_ON_FILE_STRATEGY: 'Passbolt\Log\Strategy\ActionLogsDefaultQueryStrategy'Syslog Logging:
LOG_ACTION_LOGS_ON_SYSLOG_ENABLED: "true"
LOG_ACTION_LOGS_ON_SYSLOG_STRATEGY: 'Passbolt\Log\Strategy\ActionLogsUsernameQueryStrategy'
LOG_ACTION_LOGS_ON_SYSLOG_PREFIX: 'passbolt-audit:'Strategies are independent of output method (file or syslog). Any strategy can be used with either.
ActionLogsDefaultQueryStrategy:
- Emits every action log row (all actions, success and error)
- Raw JSON with
user_id,action_id,details(no resolved usernames)
ActionLogsErrorsOnlyQueryStrategy:
- Same format as default but only failed actions
ActionLogsUsernameQueryStrategy:
- JSON with resolved usernames and full names
- Only logs specific actions: login, logout, password access/update/add/delete, share
- Does not log user creation, ownership transfers, or other actions outside its allowlist
File Logs:
- Location:
./logs/passbolt/action-logs.log(on host) - Format: Raw JSON, one entry per line
- Example:
{"id":"...","user_id":"...","action_id":"...","context":"PUT /resources/...","status":1,"created":"2025-11-24T23:11:33+00:00"}Syslog Logs:
- Location:
./logs/passbolt/syslog.log(on host) - Format: Syslog format with JSON payload
- Filter: Use
grep "passbolt-audit"to see only Passbolt entries - Example:
2025-11-24T23:11:33.093198+00:00 1d3ec95b1d81 passbolt-audit:: 2025-11-24 23:11:33 info: {"timestamp":"2025-11-24 23:11:33","user":"ada@passbolt.com","action":"password_update","context":"Ada Lovelace (ada@passbolt.com) updated password","status":1,"resource_id":"f6c326ee-c967-437a-8f9e-e163eb73c929",...}
Watch file logs in real-time:
tail -f logs/passbolt/action-logs.logWatch syslog (Passbolt entries only):
tail -f logs/passbolt/syslog.log | grep --line-buffered 'passbolt-audit'View recent Passbolt audit entries:
grep "passbolt-audit" logs/passbolt/syslog.log | tail -n 20Rsyslog sidecar container:
- Receives logs from Passbolt via shared Unix socket (
/dev/log) - Writes to
./logs/passbolt/syslog.log - Can forward to external syslog servers
Configuration:
- Rsyslog config:
config/rsyslog/rsyslog.conf - Socket: Shared via Docker named volume
syslog_socket - Documentation: https://github.com/rsyslog/rsyslog/tree/main/packaging/docker
Forward to remote syslog server:
Modify config/rsyslog/rsyslog.conf:
# Forward to remote syslog server
*.* action(type="omfwd" target="siem.example.com" port="514" protocol="udp")
The username strategy logs these actions:
user_login- User authenticationuser_logout- User logoutpassword_access- Resource viewed/accessedpassword_add- Resource createdpassword_update- Resource updatedpassword_delete- Resource deletedshare- Resource shared with users/groups
Actions not logged by username strategy (use default strategy to capture all):
- User creation/invitation
- Ownership transfers
- Folder operations
- Permission changes (outside of share action)
Passbolt does not store which URL, domain, or hostname users access in its database. To identify which users access which URLs (useful for multi-domain setups, DNS aliases, or environment tracking), correlate nginx access logs with Passbolt action logs.
Nginx access logs include the Host header (requested domain/hostname) in each request. The custom log format is configured in config/nginx/nginx-passbolt.conf:
log_format with_host '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_host"';Nginx logs (via docker compose logs passbolt):
- IP address, request path, Host header (domain/hostname), timestamp
- Example:
[27/Nov/2025:01:50:40] "POST /auth/login.json" ... "passbolt.local"
Passbolt logs (./logs/passbolt/syslog.log):
- User email, action type, timestamp
- Example:
{"timestamp":"2025-11-27 01:50:40","user":"ada@passbolt.com","action":"user_login"}
Correlation method:
- Find
POST /auth/login.jsonrequests in nginx logs (includes Host header) - Match with
user_loginactions in Passbolt syslog logs (includes user email) - Match by timestamp (±2-3 seconds) and request path
- Result:
user@email.com→domain.example.com
Example:
- Nginx:
[27/Nov/2025:01:50:40] "POST /auth/login.json" ... "passbolt.local" - Passbolt:
{"timestamp":"2025-11-27 01:50:40","user":"ada@passbolt.com","action":"user_login"} - Result:
ada@passbolt.comaccessedpassbolt.local
Parse logs with a script or log aggregation tool (ELK, Splunk, etc.) to automatically correlate and generate reports showing which users access which domains/URLs.
Stack components:
- Passbolt Pro with OIDC plugin
- Keycloak 26.4
- Shared MariaDB (
passboltandkeycloakdatabases) - Shared certificate system
Two main configuration files:
- ssl.ini - System-wide PHP SSL certificate paths:
[PHP]
openssl.cafile = /etc/ssl/certs/ca-certificates.crt
curl.cainfo = /etc/ssl/certs/ca-certificates.crt- www.conf - PHP-FPM configuration with certificate paths:
[www]
php_admin_value[openssl.cafile] = "/etc/ssl/certs/ca-certificates.crt"
php_admin_value[curl.cainfo] = "/etc/ssl/certs/ca-certificates.crt"-
Access Keycloak Admin Console:
- URL: https://keycloak.local:8443
- Username: admin
- Password: admin
-
Create Realm:
- Click "Create Realm"
- Name: "passbolt"
- Click "Create"
-
Create Client:
- Go to "Clients" > "Create client"
- Client type: OpenID Connect
- Client ID: "passbolt-client"
- Client authentication: ON
- Authorization: OFF
- Click "Save"
-
Configure Client Settings:
- Settings tab:
- Valid redirect URIs: https://passbolt.local/auth/login
- Web origins: https://passbolt.local
- Click "Save"
- Credentials tab:
- Copy the generated "Client secret" value
- Settings tab:
-
Create User:
- Go to "Users" > "Add user"
- Username: ada
- Email: ada@passbolt.com (must match Passbolt admin email)
- First name: Ada
- Last name: Lovelace
- Click "Create"
- Go to "Credentials" tab:
- Set password: passbolt
- Temporary: OFF
- Click "Set password"
Configure in Passbolt web interface under Administration → Authentication → SSO:
- Issuer URL:
https://keycloak.local:8443/realms/passbolt - OpenID Configuration Path:
/.well-known/openid-configuration - Client ID:
passbolt-client - Client Secret: Use value from Keycloak Credentials tab
- Scopes:
openid profile email - SSL Verification: Enabled
Full OpenID Configuration URL: https://keycloak.local:8443/realms/passbolt/.well-known/openid-configuration
This endpoint provides the complete OAuth2/OIDC discovery document, including authorization, token, and userinfo endpoints.
Verify Configuration:
curl -k https://keycloak.local:8443/realms/passbolt/.well-known/openid-configuration | jqNote: -k flag skips certificate verification for self-signed certificates in development.
Expected Output (excerpt):
{
"issuer": "https://keycloak.local:8443/realms/passbolt",
"authorization_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/auth",
"token_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/token",
"userinfo_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/userinfo",
...
}- Access Passbolt at https://passbolt.local
- Click "SSO Login"
- Redirected to Keycloak
- Log in with ada@passbolt.com / passbolt
- Redirected back to Passbolt and logged in
Note: Configuration examples in assets/ directory.
SMTP4Dev: https://smtp.local (SMTPS port 465)
Passbolt configured to use SMTP4Dev with SMTPS (implicit TLS):
# Email Configuration
EMAIL_TRANSPORT_DEFAULT_HOST: "ssl://smtp.local"
EMAIL_TRANSPORT_DEFAULT_PORT: 25
EMAIL_TRANSPORT_DEFAULT_USERNAME: ""
EMAIL_TRANSPORT_DEFAULT_PASSWORD: ""
EMAIL_DEFAULT_FROM: "admin@passbolt.com"
# SMTP Settings (SMTPS - implicit TLS)
# Note: SMTP security settings are configured via Passbolt Web UI# Create certificates directory
mkdir -p smtp4dev/certs
# Generate private key and corresponding certificate
openssl req -x509 -newkey rsa:4096 \
-keyout smtp4dev/certs/tls.key \
-out smtp4dev/certs/tls.crt \
-days 365 -nodes \
-subj "/CN=smtp.local"
# Create PKCS12 bundle
openssl pkcs12 -export \
-out smtp4dev/certs/tls.pfx \
-inkey smtp4dev/certs/tls.key \
-in smtp4dev/certs/tls.crt \
-passout pass:changeme- Create a test user in Passbolt
- Check for registration email in SMTP4Dev web interface
- Verify email content and headers
OpenPGP uses public-key cryptography: each user has a key pair (private + public). In passbolt, users generate or import GPG keys through the browser extension during account setup.
Note: passbolt handles GPG operations automatically through the browser extension. Users typically don't need to use command-line GPG tools.
User Authentication:
- Each user has a GPG key pair (private + public)
- Private key stored in browser extension (never sent to server)
- Public key stored on server for encryption
- User authenticates by decrypting server challenges with private key
Secret Encryption:
- Hybrid encryption model: session keys + GPG
- Secrets encrypted with random session keys (symmetric)
- Session keys encrypted with recipient public keys (asymmetric GPG)
- Each recipient gets their own encrypted session key
- Messages can be encrypted for main key or subkey (both supported)
- Browser extension (OpenPGP.js) handles encryption/decryption client-side
Metadata Encryption (v5):
- Separate GPG key pairs for encrypting metadata (resource names, URIs, descriptions)
- Metadata keys are ECC (Ed25519) GPG keys generated per user
- Two types of metadata encryption:
- Personal resources: Encrypted with user's main GPG key (
metadata_key_type: user_key,metadata_key_id: null) - Shared resources: Encrypted with user's metadata keys (
metadata_key_type: shared_key,metadata_key_id: UUID)
- Personal resources: Encrypted with user's main GPG key (
- Metadata private keys encrypted with user's main GPG key and stored in database
- Encrypted metadata stored as OpenPGP armored messages in
metadatacolumn (JSON containing name, uri, description) - Same GPG validation rules apply
Database Storage:
gpgkeystable: Stores user public keys (GPG armored format inarmored_keycolumn)- One active key per user (
user_id,deleted = false) fingerprint(unique),key_id,type,bits,uid,key_created,expires- Used for encrypting secrets and session keys for recipients
- One active key per user (
metadata_keystable: Stores metadata public keys (GPG armored format inarmored_keycolumn,fingerprintfor key identification)metadata_private_keystable: Stores encrypted metadata private keys per user (encrypted with user's main GPG key, indatacolumn as OpenPGP message)- Multiple users can share the same
metadata_key_id(same public key, different encrypted private keys per user) user_idlinks encrypted private key to specific user
- Multiple users can share the same
resourcestable:metadatacolumn (MEDIUMTEXT) stores encrypted metadata as OpenPGP armored message (JSON with name, uri, description)metadata_key_idreferences which metadata key was used (can be set for both types)metadata_key_typeindicates encryption method (user_keyorshared_key)- Legacy columns (
name,uri,description) still exist for v4 resources
folderstable: Similar structure (metadata,metadata_key_id,metadata_key_typecolumns)- Metadata keys are shared across users; each user has their own encrypted copy of the private key
Key Management:
- Users have one active public key at a time (stored in
gpgkeystable) - Public keys retrieved from database when encrypting for recipients
- Metadata keys can be rotated: create new key, expire old key, re-encrypt resources, delete old key
- Metadata keys can be expired (user keys cannot have expiry dates)
- Keys can be soft-deleted (
deletedflag) - Keys validated for encryption capability before use
- Revocation checking: revoked keys rejected (RSA only; ECC revocation checking not yet supported)
Key Identifiers:
- Fingerprint: 40 hex characters, primary identifier (e.g.,
ABCDEF1234...) - Key ID: 8 (short) or 16 (long) hex characters
- Long key IDs recommended for security (short IDs vulnerable to collision)
Browser Extension:
- Uses OpenPGP.js library for all GPG operations
- Private keys never leave the browser
- Decryption happens client-side
- Supports both RSA and ECC keys
- Key generation during user setup (client-side)
- Account recovery: GPG keys used to decrypt recovery data
Message Validation:
- OpenPGP messages validated for parsing, format, and structure
- Recipient validation: messages must be encrypted for intended recipient's key/subkey
- Symmetric and asymmetric packet validation
- Signature verification for authentication challenges and signed data
passbolt supports both RSA and ECC (Elliptic Curve Cryptography) GPG keys:
RSA Keys:
- Traditional key type (supported since early versions)
- Allowed sizes: 2048 (non-strict), 3072, 4096 bits
- Strict mode (recommended): 3072 or 4096 bits only
- Larger key sizes provide stronger security but slower operations
ECC Keys (v5.6.0+):
- Modern Ed25519/Curve25519 (default for new users since v5.6.0)
- Curve format:
curve25519_legacy+ed25519_legacy - Comparable security to RSA-3072 with better performance
- Smaller payload size, faster encryption/decryption
Key Validation:
- Supported algorithms: RSA, ECC, ECDSA, DH (strict mode excludes DSA, ELGAMAL)
- No expiry dates allowed
- Keys must not be expired or revoked
- Keys must not contain multiple main packets
- Fingerprint: 40 hex characters
- Key ID: 8 or 16 hex characters (long IDs recommended)
- Email must be present in key UID and match user email
- Subkeys required: ECDH subkey for ECC, RSA subkey for RSA
- Keys without subkeys: messages encrypted for main key ID (legacy support)
Configuration (environment variables):
PASSBOLT_PLUGINS_USER_KEY_POLICIES_PREFERRED_KEY_TYPE-rsaorcurve(default:curve)PASSBOLT_PLUGINS_USER_KEY_POLICIES_PREFERRED_KEY_SIZE- RSA key size:3072or4096(null for ECC)PASSBOLT_PLUGINS_USER_KEY_POLICIES_PREFERRED_KEY_CURVE- ECC curve:curve25519_legacy+ed25519_legacy(default)
Reference: passbolt User Key Policies Configuration
Demo GPG keys for passbolt users are in keys/gpg/. Generated by scripts/gpg/generate-demo-keys.sh, these are private keys (.key) and public keys (.pub) for demo accounts.
For passbolt login: Import the private key (.key file) into your passbolt account during setup via the browser extension. Passphrase is the user's email address.
passbolt stores its server GPG keyring at /var/lib/passbolt/.gnupg in the container. Server keys are imported from /etc/passbolt/gpg/serverkey_private.asc on startup.
Server Key Purpose:
- Server signing, encryption, decryption, and verification operations
- Used for internal server operations (not user authentication)
- Server key fingerprint and passphrase stored in configuration
- Server key imported into container keyring on startup
# List keys in container
docker compose exec passbolt su -s /bin/bash -c "gpg --home /var/lib/passbolt/.gnupg --list-keys" www-data
# Export server public key
docker compose exec passbolt su -s /bin/bash -c "gpg --home /var/lib/passbolt/.gnupg --armor --export" www-data
# Import key into container keyring
docker compose exec passbolt su -s /bin/bash -c "gpg --home /var/lib/passbolt/.gnupg --import" www-data < keyfile.ascThe passbolt server keyring at /var/lib/passbolt/.gnupg contains:
.gnupg/
├── pubring.kbx # Public keys database
├── pubring.kbx~ # Backup of public keys database
├── trustdb.gpg # Trust database
├── private-keys-v1.d/ # Private keys (encrypted, one file per key)
├── S.gpg-agent* # GPG agent sockets
└── random_seed # Entropy pool
Note: User keys are stored in the browser extension, not on the server. Only the server's own GPG keys are in this directory.
./scripts/ldap/users/add.sh "Firstname" "Lastname" "username@passbolt.com"
# Example: Add Edith Clarke
./scripts/ldap/users/add.sh "Edith" "Clarke" "edith@passbolt.com"./scripts/ldap/add-edith.sh# Check if user was added to LDAP
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "dc=passbolt,dc=local" "(cn=username)"
# Run manual sync in Passbolt:
# 1. Log in to Passbolt as an administrator
# 2. Go to Organization Settings > Directory Synchronization
# 3. Click 'Synchronize Now'./scripts/ldap/groups/add.sh <groupname> "<description>" [user1 user2 ...]
# Examples:
./scripts/ldap/groups/add.sh developers "Development Team" ada betty
./scripts/ldap/groups/add.sh developers "Development Team" # Empty group./scripts/ldap/groups/remove.sh <groupname>./scripts/ldap/groups/add-user.sh <username> <groupname>./scripts/ldap/groups/remove-user.sh <username> <groupname>docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "dc=passbolt,dc=local" "(cn=<groupname>)"Before testing user removal, ensure Passbolt is configured to suspend users rather than delete them:
- Log in to Passbolt as an administrator
- Go to Organization Settings > Directory Synchronization
- Under "Synchronization Options", set:
- "Delete Users" to "No"
- "Default Group Manager" to your preferred setting
- "Default Group Admin" to your preferred setting
# Remove user from LDAP
./scripts/ldap/users/remove.sh <username>
# Run manual sync in Passbolt to suspend the user# Add user back to LDAP
./scripts/ldap/users/add.sh "Firstname" "Lastname" "username@passbolt.com"
# Run manual sync in Passbolt to reactivate the user# Test LDAPS connection from Passbolt container
docker compose exec passbolt openssl s_client -connect ldap:636 \
-servername ldap.local -CAfile /etc/ssl/certs/ldaps_bundle.crt -brief
# Test LDAPS connection from host (if certificates are trusted)
openssl s_client -connect ldap.local:636 -servername ldap.local -brief# Check SMTP4Dev logs
docker compose logs smtp4dev
# Check Passbolt email logs
docker compose logs passbolt | grep -i email# Test Valkey connectivity
docker compose exec valkey valkey-cli ping
# List active sessions
docker compose exec valkey valkey-cli keys "*session*"./scripts/tests/integration/test-ldap.sh./scripts/tests/sync/test-sync.sh./scripts/tests/scripts/test-scripts.shTest Passbolt's SCIM (System for Cross-domain Identity Management) endpoints using Bruno API client.
- Install Bruno: Download from usebruno.com
- Open Collection: Open the
bruno/passbolt-scim-testingfolder in Bruno - Configure Environment: The
localenvironment is pre-configured with:- Base URL:
https://passbolt.local - SCIM Base URL:
https://passbolt.local/scim/v2/935452c2-7a21-4413-8457-8085b50376d3 - Bearer Token:
pb_wwpDka8H0AORBBVGcL8tU8xtJTJJI0etBO7F0QeshLj - Content-Type:
application/scim+json
- Base URL:
- Get Service Provider Config - Verify SCIM is enabled
- List Users - See existing users
- Create User - Add a test user
- Get User by ID - Verify user creation
- Update User - Modify user attributes
- Patch User - Partial update (e.g., deactivate)
- Search Users - Find users with filters
- Delete User - Remove test user
- Email Type: Passbolt requires
"type": "work"in email objects - Authentication: Uses Bearer token authentication
- Content-Type: Must be
application/scim+json - User Schema: Requires
userName,name, andemailswith work type
curl -k --request GET \
--url 'https://passbolt.local/scim/v2/935452c2-7a21-4413-8457-8085b50376d3/Users?startIndex=1&count=100' \
--header 'authorization: Bearer pb_wwpDka8H0AORBBVGcL8tU8xtJTJJI0etBO7F0QeshLj' \
--header 'content-type: application/scim+json'Note: -k flag skips certificate verification for self-signed certificates in development.
- "Email not found" error: Ensure email has
"type": "work" - Authentication errors: Verify bearer token is correct in environment
- User not found: Check if user exists in Passbolt first
# Check LDAP server status
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "dc=passbolt,dc=local" "(objectClass=*)"
# Check Passbolt logs
docker compose logs passbolt
# Check LDAP logs
docker compose logs ldapSymptoms: verify error:num=19:self-signed certificate in certificate chain or "Can't contact LDAP server"
Root Cause: The LDAP server uses its own self-signed certificate issued by docker-light-baseimage, but the certificate bundle is missing the CA certificate or contains incorrect certificates.
Solutions:
-
Verify the certificate bundle contains both server and CA certificates:
# Check certificate count (should be 2) grep -c "BEGIN CERTIFICATE" certs/ldaps_bundle.crt # Check server certificate subject openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 2 -B 2 "Subject:" | head -3 # Should show: Subject: CN=ldap.local
-
If the bundle is incorrect, regenerate it:
# Run the certificate fix script (extracts from container) ./scripts/fix-ldaps-certificates.sh # Rebuild Passbolt container to pick up new bundle docker compose build passbolt docker compose up -d passbolt
-
Verify the certificate SAN matches:
openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 5 "Subject Alternative Name" # Should show: DNS:ldap.local
# Step 1: Generate new certificate hierarchy
./scripts/generate-certificates.sh
# Step 2: Deploy certificates to LDAP container (optional - for custom certificates)
./scripts/setup-ldap-certs.sh
# Step 3: Fix LDAPS bundle for Passbolt (extracts from container)
./scripts/fix-ldaps-certificates.sh
# Step 4: Rebuild Passbolt container to pick up new certificate bundle
docker compose build passbolt
docker compose up -d passbolt# Verify LDAP certificate details
openssl x509 -in ldap-certs/ldap.crt -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)"
# Verify certificate chain integrity
openssl verify -CAfile keys/rootCA.crt ldap-certs/ldap.crt
# Verify LDAPS bundle (used by Passbolt)
openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)"Symptoms: Passbolt cannot connect to LDAP server
Solutions:
- Verify LDAP server is running with TLS enabled
- Check that custom certificates are properly deployed
- Ensure network connectivity between containers
docker compose exec ldap ldapsearch -x -H ldaps://localhost:636 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "dc=passbolt,dc=local" "(objectClass=*)"Symptoms: Users appear in LDAP but not in Passbolt
Solutions:
- Verify LDAP search filters are correct
- Check that users have the required
objectClassattributes - Ensure the bind DN has proper search permissions
- Run manual synchronization in Passbolt UI
- Check that users have valid email addresses (required for Passbolt)
Note: Remember that sync is one-way from LDAP to Passbolt. Changes to user data must be made in LDAP, not in Passbolt.
Symptoms: "Can't contact LDAP server" during bind attempts
Root Cause: The LDAP admin user cn=admin,dc=passbolt,dc=local may be missing from the LDAP directory.
Solutions:
-
Check if admin user exists:
docker exec ldap ldapsearch -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -b "dc=passbolt,dc=local" -x "(cn=admin)"
-
If admin user is missing, create it:
# Create admin user LDIF file cat > certs/admin_user.ldif << EOF dn: cn=admin,dc=passbolt,dc=local objectClass: simpleSecurityObject objectClass: organizationalRole cn: admin description: LDAP administrator userPassword: P4ssb0lt EOF # Add admin user to LDAP docker cp certs/admin_user.ldif ldap:/tmp/admin_user.ldif docker exec ldap ldapadd -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -f /tmp/admin_user.ldif
-
Verify admin user was created:
docker exec ldap ldapsearch -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -b "dc=passbolt,dc=local" -x "(cn=admin)"
Symptoms: Group memberships not syncing to Passbolt
Solutions:
- Verify users have activated their Passbolt accounts
- Check that groups use
groupOfUniqueNamesobject class - Ensure member references use full DNs
Symptoms: Users sync to Passbolt but group memberships fail to sync, or incorrect users appear in groups
Root Cause: The LDAP meta backend's suffixmassage feature doesn't automatically transform DN references in attributes like uniqueMember. Group memberships contain DNs in the original backend format instead of the unified namespace format.
Diagnosis:
# Check group membership DNs through aggregator
docker exec ldap-meta ldapsearch -H ldap://localhost:389 -D "cn=admin,dc=unified,dc=local" -w "secret" -b "dc=unified,dc=local" "(cn=operations)"
# Check user DN in unified namespace
docker exec ldap-meta ldapsearch -H ldap://localhost:389 -D "cn=admin,dc=unified,dc=local" -w "secret" -b "dc=unified,dc=local" "(mail=user@example.com)"Solution: The setup scripts have been updated to use the correct DN format from the start. If you encounter this issue with existing deployments, update group membership DNs to use the unified namespace format:
# Create LDIF file to fix group memberships
cat > fix_group_memberships.ldif << EOF
# Fix operations group
dn: cn=operations,ou=teams,dc=example,dc=com
changetype: modify
replace: uniqueMember
uniqueMember: cn=User Name,ou=people,dc=example,dc=unified,dc=local
# Fix project-teams group
dn: cn=project-teams,ou=teams,dc=example,dc=com
changetype: modify
replace: uniqueMember
uniqueMember: cn=User Name,ou=people,dc=example,dc=unified,dc=local
EOF
# Apply the fix to backend LDAP server
docker exec -i ldap2 ldapmodify -H ldap://localhost:389 -D "cn=admin,dc=example,dc=com" -w "Ex4mple123" < fix_group_memberships.ldif
# Clean up
rm fix_group_memberships.ldifPrevention: The setup scripts now create groups with the correct DN format automatically. This issue should not occur in fresh deployments.
Verification: After applying the fix, run directory sync in Passbolt to verify group memberships are correctly synchronized.
Symptoms: SSO login doesn't work
Solutions:
- Verify the client ID and secret match between Keycloak and Passbolt
- Check that the redirect URI is correctly configured
- Ensure the user exists in both systems with matching email addresses
Symptoms: Keycloak fails to connect to the database
Solutions:
- Check database credentials in docker-compose.yaml
- Verify the keycloak database exists and permissions are set
- Check MariaDB logs for connection errors
Symptoms: Passbolt emails not being sent
Solutions:
- Check certificate files exist and have correct permissions
- Verify SMTP configuration in Passbolt
- Check SMTP4Dev logs for connection issues
Symptoms: Session handling failures, users getting logged out unexpectedly
Solutions:
- Verify Valkey container is running:
docker compose ps valkey - Check Valkey connectivity:
docker compose exec passbolt ping valkey - Check Valkey logs:
docker compose logs valkey
Symptoms: "no valid configuration found in file: /traefik.yaml"
Cause: Improperly indented YAML (usually from copying Passbolt docs).
Fix:
# Validate config
./scripts/validate-traefik-config.sh
# Check logs
docker compose logs traefik
# Verify indentation (use 2 spaces, no tabs)
grep -P '\t' config/traefik/*.yaml # Should return nothingYAML must use consistent spacing:
# Wrong
api:
dashboard: true
# Correct
api:
dashboard: trueSymptoms: 404 errors, services not accessible
Fix:
- Check dashboard: http://localhost:8080
- Verify certificates exist in
keys/ - Confirm Docker socket mounted:
/var/run/docker.sock:/var/run/docker.sock:ro - Check service has
traefik.enable=truelabel
Send a test email using SMTP4Dev API:
curl -k -X POST https://smtp.local/api/v2/messages \
-H "Content-Type: application/json" \
-d '{
"to": "test@example.com",
"subject": "Test Email",
"body": "This is a test email from Passbolt"
}"Symptoms: Docker mount errors or "permission denied"
Solutions:
chmod 644 certs/*.crt smtp4dev/certs/*.crt
chmod 600 keys/*.key smtp4dev/certs/*.key
chmod 644 smtp4dev/certs/tls.pfxSymptoms: LDAPS connection fails after modifying docker-compose.yaml
Root Cause: Adding incorrect environment variables to the osixia/openldap container can break its internal certificate management.
Solutions:
-
Use only documented environment variables from the osixia/openldap documentation:
# Basic Configuration LDAP_ORGANISATION: "Passbolt" LDAP_DOMAIN: "passbolt.local" LDAP_BASE_DN: "dc=passbolt,dc=local" LDAP_ADMIN_PASSWORD: "P4ssb0lt" LDAP_CONFIG_PASSWORD: "P4ssb0lt" # TLS Configuration LDAP_TLS: "true" LDAP_TLS_VERIFY_CLIENT: "never" # Readonly User LDAP_READONLY_USER: "true" LDAP_READONLY_USER_USERNAME: "readonly" LDAP_READONLY_USER_PASSWORD: "readonly"
-
Avoid custom LDAP_TLS_ variables* that are not in the official documentation
-
Restart the LDAP container after changes:
docker compose restart ldap
Symptoms: "Can't contact LDAP server" or certificate verification failures
Root Cause: The osixia/openldap image generates its own self-signed certificates using the docker-light-baseimage CA.
Solutions:
-
Verify the certificate chain:
# Check LDAP server certificate docker compose exec ldap openssl x509 -in /container/service/slapd/assets/certs/ldap.crt -text -noout | grep -A 2 -B 2 "Subject:" # Check CA certificate docker compose exec ldap openssl x509 -in /container/service/slapd/assets/certs/ca.crt -text -noout | grep -A 2 -B 2 "Subject:"
-
Verify certificate bundle in Passbolt:
# Check the certificate bundle used by Passbolt openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 2 -B 2 "Subject:" # Should show: CN=ldap.local
-
Regenerate certificate bundle if needed:
./scripts/fix-ldaps-certificates.sh docker compose build passbolt docker compose up -d passbolt
Symptoms: Passbolt cannot connect to LDAP server
Solutions:
- Verify LDAP server is running with TLS enabled
- Check that custom certificates are properly deployed
- Ensure network connectivity between containers
docker compose exec ldap ldapsearch -x -H ldaps://localhost:636 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "dc=passbolt,dc=local" "(objectClass=*)"Symptoms: STARTTLS works externally but not from Passbolt container
Root Cause: This is expected behavior. The osixia/openldap image supports both:
- LDAPS (implicit TLS) on port 636 - used by Passbolt
- LDAP with STARTTLS on port 389 - another option for Passbolt
Solutions:
- For Passbolt: Use LDAPS on port 636 (current configuration)
- For another option: Use LDAP with STARTTLS on port 389
- Both methods work with the same certificate configuration
ls -la certs/ldaps_bundle.crt
ls -la smtp4dev/certs/tls.crt smtp4dev/certs/tls.pfx
ls -la ldap-certs/ldap.crt ldap-certs/ldap.key ldap-certs/ca.crt
ls -la keys/rootCA.crt keys/keycloak.crtls -la subscription_key.txt
# Should show: lrwxr-xr-x ... subscription_key.txt -> /path/to/actual/keyIf backup size increases unexpectedly, use the diagnostic script to identify the cause:
# Run against local demo database (formatted columns)
docker compose exec -T db mariadb -u passbolt -pP4ssb0lt passbolt < scripts/diagnose-db-growth.sql | column -t
# Or save to file
docker compose exec -T db mariadb -u passbolt -pP4ssb0lt passbolt < scripts/diagnose-db-growth.sql > db-diagnosis.txt
# Or against production MySQL (formatted)
mysql -u user -p database < scripts/diagnose-db-growth.sql | column -tInterpreting Results:
- Largest tables - Focus on tables >100MB.
action_logsis often the culprit. - Soft deletes - High
deletedcounts mean records aren't being purged. Example:users 37 total, 25 deleted= 68% are soft-deleted. - Action logs - Check
logs_last_30dvslogs_last_60d. Rapid growth indicates database logging even with file logging enabled. - History tables -
*_historytables accumulate over time. Check their sizes. - Secrets table -
total_secret_data_mbshows actual encrypted data size. Compareavg_secret_size_bytesbefore/after v5 migration. - V5 migration -
first_createddate shows when v5 migration occurred. Correlate with backup size increase timeline. - Email queue - Stuck emails accumulate if not processed.
- Binary logs - Check separately as root (see "Possible causes" below for commands). File-level backups include binary logs (mysqldump does not).
Possible causes:
- Binary logs not purged - MySQL 8.0+ enables binary logging by default (5.7 and earlier: disabled). If
expire_logs_days=0orbinlog_expire_logs_seconds=0, logs never auto-purge. File-level backups include binary logs (mysqldump does not). Check as root:Even with "no significant API hits", internal writes (syncs, maintenance) generate binary log entries.mysql -u root -p -e "SHOW BINARY LOGS;" mysql -u root -p -e "SHOW VARIABLES LIKE 'expire_logs_days';"
- Action logs in database (even with file logging enabled)
- Soft deletes accumulating over time
- V5 encryption increasing secret sizes
docker compose logs passbolt
docker compose logs -f passboltdocker compose logs ldap
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
-D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
-b "cn=config" "(objectClass=*)"Key directories:
scripts/- Setup and management scriptscerts/- Certificate files (LDAPS bundle, SMTP certificates)config/- Configuration files (PHP, SSL, database)assets/- Documentation screenshotsdocker-compose.yaml- Main configuration
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a new Pull Request
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License (AGPL) as published by the Free Software Foundation version 3.
The name "Passbolt" is a registered trademark of Passbolt SA, and Passbolt SA hereby declines to grant a trademark license to "Passbolt" pursuant to the GNU Affero General Public License version 3 Section 7(e), without a separate agreement with Passbolt SA.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see GNU Affero General Public License v3.