# Casdoor SSO Identity Provider Casdoor provides Single Sign-On (SSO) authentication for Agathos services. This document covers the design decisions, architecture, and deployment procedures. ## Design Philosophy ### Security Isolation Casdoor handles identity and authentication - the most security-sensitive data in any system. For this reason, Casdoor uses a **dedicated PostgreSQL instance** on Titania rather than sharing the PostgreSQL server on Portia with other applications. This isolation provides: - **Data separation**: Authentication data is physically separated from application data - **Access control**: The `casdoor` database user only has access to the `casdoor` database - **Blast radius reduction**: A compromise of the shared database on Portia doesn't expose identity data - **Production alignment**: Dev/UAT/Prod environments use the same architecture ### Native PostgreSQL with Docker Casdoor The architecture splits cleanly: ``` ┌──────────────────────────────────────────────────────────────┐ │ titania.incus │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Native PostgreSQL 17 (systemd) │ │ │ │ - SSL enabled for external connections │ │ │ │ - Local connections without SSL │ │ │ │ - Managed like any standard PostgreSQL install │ │ │ │ - Port 5432 │ │ │ └────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ localhost:5432 │ │ │ sslmode=disable │ │ │ │ │ ┌────────┴───────────────────────────────────────────────┐ │ │ │ Casdoor Docker Container (network_mode: host) │ │ │ │ - Runs as casdoor:casdoor user │ │ │ │ - Only has access to its database │ │ │ │ - Cannot touch PostgreSQL server config │ │ │ │ - Port 22081 (via HAProxy) │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ External: SSL required │ sslmode=verify-ca ▼ ┌─────────────┐ │ PGadmin │ │ on Portia │ └─────────────┘ ``` ### Why Not Docker for PostgreSQL? Docker makes PostgreSQL permission management unnecessarily complex: - UID/GID mapping between host and container - Volume permission issues - SSL certificate ownership problems - More difficult backups and maintenance Native PostgreSQL is: - Easier to manage (standard Linux administration) - Better integrated with systemd - Simpler backup procedures - Well-documented and understood ### SSL Strategy PostgreSQL connections follow a **split SSL policy**: | Connection Source | SSL Requirement | Rationale | |-------------------|-----------------|-----------| | Casdoor (localhost) | `sslmode=disable` | Same host, trusted | | PGadmin (Portia) | `sslmode=verify-ca` | External network, requires encryption | | Other external | `hostssl` required | Enforced by pg_hba.conf | This is controlled by `pg_hba.conf`: ``` # Local connections (Unix socket) local all all peer # Localhost connections (no SSL required) host all all 127.0.0.1/32 md5 # External connections (SSL required) hostssl all all 0.0.0.0/0 md5 ``` ### System User Pattern The Casdoor service user is created without hardcoded UID/GID: ```yaml - name: Create casdoor user ansible.builtin.user: name: "{{ casdoor_user }}" system: true # System account, UID assigned by OS ``` The playbook queries the assigned UID/GID at runtime for Docker container user mapping. ## Architecture ### Components | Component | Location | Purpose | |-----------|----------|---------| | PostgreSQL 17 | Native on Titania | Dedicated identity database | | Casdoor | Docker on Titania | SSO identity provider | | HAProxy | Titania | TLS termination, routing | | Alloy | Titania | Syslog collection | ### Deployment Order ``` 1. postgresql_ssl/deploy.yml → Install PostgreSQL, SSL, create casdoor DB 2. casdoor/deploy.yml → Deploy Casdoor container 3. pgadmin/deploy.yml → Distribute SSL cert to PGadmin (optional) ``` ### Network Ports | Port | Service | Access | |------|---------|--------| | 22081 | Casdoor HTTP | Via HAProxy (network_mode: host) | | 5432 | PostgreSQL | SSL for external, plain for localhost | | 51401 | Syslog | Local only (Alloy) | ### Data Persistence PostgreSQL data (native install): ``` /var/lib/postgresql/17/main/ # Database files /etc/postgresql/17/main/ # Configuration /etc/postgresql/17/main/ssl/ # SSL certificates ``` Casdoor configuration: ``` /srv/casdoor/ ├── conf/ │ └── app.conf # Casdoor configuration └── docker-compose.yml # Service definition ``` ## Prerequisites ### 1. Terraform (S3 Buckets) Casdoor can use S3-compatible storage for avatars and attachments: ```bash cd terraform terraform apply ``` ### 2. Ansible Vault Secrets Add to `ansible/inventory/group_vars/all/vault.yml`: ```yaml # PostgreSQL SSL postgres user password (for Titania's dedicated PostgreSQL) vault_postgresql_ssl_postgres_password: "secure-postgres-password" # Casdoor database password vault_casdoor_db_password: "secure-db-password" # Casdoor application secrets vault_casdoor_auth_state: "random-32-char-string" vault_casdoor_app_client_secret: "generated-client-secret" # Casdoor initial user passwords (changed after first login) vault_casdoor_admin_password: "initial-admin-password" vault_casdoor_hostmaster_password: "initial-hostmaster-password" # Optional (for RADIUS protocol) vault_casdoor_radius_secret: "radius-secret" ``` Generate secrets: ```bash # Database password openssl rand -base64 24 # Auth state openssl rand -hex 16 ``` ### 3. Alloy Log Collection Ensure Alloy is deployed to receive syslog: ```bash ansible-playbook alloy/deploy.yml --limit titania.incus ``` ## Deployment ### Fresh Installation ```bash cd ansible # 1. Deploy PostgreSQL with SSL ansible-playbook postgresql_ssl/deploy.yml # 2. Deploy Casdoor ansible-playbook casdoor/deploy.yml # 3. Update PGadmin with SSL certificate (optional) ansible-playbook pgadmin/deploy.yml ``` ### Verify Deployment ```bash # Check PostgreSQL status ssh titania.incus "sudo systemctl status postgresql" # Check Casdoor container ssh titania.incus "cd /srv/casdoor && docker compose ps" # Check logs ssh titania.incus "cd /srv/casdoor && docker compose logs --tail=50" # Test health endpoint curl -s http://titania.incus:22081/api/health ``` ### Redeployment To redeploy Casdoor only (database preserved): ```bash ansible-playbook casdoor/remove.yml ansible-playbook casdoor/deploy.yml ``` To completely reset (including database): ```bash ansible-playbook casdoor/remove.yml ssh titania.incus "sudo -u postgres dropdb casdoor" ssh titania.incus "sudo -u postgres dropuser casdoor" ansible-playbook postgresql_ssl/deploy.yml ansible-playbook casdoor/deploy.yml ``` ## Configuration Reference ### Host Variables Located in `ansible/inventory/host_vars/titania.incus.yml`: ```yaml # PostgreSQL SSL (dedicated identity database) postgresql_ssl_postgres_password: "{{ vault_postgresql_ssl_postgres_password }}" postgresql_ssl_port: 5432 postgresql_ssl_cert_path: /etc/postgresql/17/main/ssl/server.crt # Casdoor service account (system-assigned UID/GID) casdoor_user: casdoor casdoor_group: casdoor casdoor_directory: /srv/casdoor # Web casdoor_port: 22081 casdoor_runmode: dev # or 'prod' # Database (connects to localhost PostgreSQL) casdoor_db_port: 5432 casdoor_db_name: casdoor casdoor_db_user: casdoor casdoor_db_password: "{{ vault_casdoor_db_password }}" casdoor_db_sslmode: disable # Localhost, no SSL needed # Logging casdoor_syslog_port: 51401 ``` ### SSL Certificate The self-signed certificate is generated automatically with: - **Common Name**: `titania.incus` - **Subject Alt Names**: `titania.incus`, `localhost`, `127.0.0.1` - **Validity**: 10 years (`+3650d`) - **Key Size**: 4096 bits - **Location**: `/etc/postgresql/17/main/ssl/` To regenerate certificates: ```bash ssh titania.incus "sudo rm -rf /etc/postgresql/17/main/ssl/*" ansible-playbook postgresql_ssl/deploy.yml ansible-playbook pgadmin/deploy.yml # Update cert on Portia ``` ## PGadmin Connection To connect from PGadmin on Portia: 1. Navigate to https://pgadmin.ouranos.helu.ca 2. Add Server: - **General tab** - Name: `Titania PostgreSQL (Casdoor)` - **Connection tab** - Host: `titania.incus` - Port: `5432` - Database: `casdoor` - Username: `casdoor` - Password: *(from vault)* - **SSL tab** - SSL Mode: `Verify-CA` - Root certificate: `/var/lib/pgadmin/certs/titania-postgres-ca.crt` The certificate is automatically distributed by `ansible-playbook pgadmin/deploy.yml`. ## Application Branding & CSS Customization Casdoor allows extensive customization of login/signup pages through CSS and HTML fields in the **Application** settings. ### Available CSS/HTML Fields | Field | Purpose | Where Applied | |-------|---------|---------------| | `formCss` | Custom CSS for desktop login forms | Login, signup, consent pages | | `formCssMobile` | Mobile-specific CSS overrides | Mobile views | | `headerHtml` | Custom HTML in page header | All auth pages (can inject `", "footerHtml": "
Powered by Helu.ca
", "headerHtml": "", "formBackgroundUrl": "https://example.com/bg.jpg" } ] } ``` ### Example: Custom Theme CSS The `formCss` field contains CSS to customize the Ant Design components: ```css ``` ### Example: Custom Footer Replace the default "Powered by Casdoor" footer: ```html
Powered by Helu.ca
``` ### Organization-Level Theme Organization settings also affect theming. Configure in the **Organization** settings: | Setting | Purpose | |---------|---------| | `themeData.colorPrimary` | Primary color (Ant Design) | | `themeData.borderRadius` | Border radius for components | | `themeData.isCompact` | Compact mode toggle | | `logo` | Organization logo | | `favicon` | Browser favicon | | `websiteUrl` | Organization website | ### Updating Existing Applications Changes to `init_data.json` only apply during **initial Casdoor setup**. For existing deployments: 1. **Via Admin UI**: Applications → Edit → Update CSS/HTML fields 2. **Via API**: Use Casdoor's REST API to update application settings 3. **Database reset**: Redeploy with `initDataNewOnly = false` (overwrites existing data) ### CSS Class Reference Common CSS classes for targeting Casdoor UI elements: | Class | Element | |-------|---------| | `.login-panel` | Main login form container | | `.login-logo-box` | Logo container | | `.login-username` | Username input wrapper | | `.login-password` | Password input wrapper | | `.login-button-box` | Submit button container | | `.login-forget-password` | Forgot password link | | `.login-signup-link` | Signup link | | `.login-languages` | Language selector | | `.back-button` | Back button | | `.provider-img` | OAuth provider icons | | `.signin-methods` | Sign-in method tabs | | `.verification-code` | Verification code input | | `.login-agreement` | Terms agreement checkbox | ## Initial Setup After deployment, access Casdoor at https://id.ouranos.helu.ca: 1. **Login** with default credentials: `admin` / `123` 2. **Change admin password immediately** 3. **Create organization** for your domain 4. **Create applications** for services that need SSO: - SearXNG (via OAuth2-Proxy) - Grafana - Other internal services ### OAuth2 Application Setup For each service: 1. Applications → Add 2. Configure OAuth2 settings: - Redirect URI: `https://service.ouranos.helu.ca/oauth2/callback` - Grant types: Authorization Code 3. Note the Client ID and Client Secret for service configuration ## Troubleshooting ### PostgreSQL Issues ```bash # Check PostgreSQL status ssh titania.incus "sudo systemctl status postgresql" # View PostgreSQL logs ssh titania.incus "sudo journalctl -u postgresql -f" # Check SSL configuration ssh titania.incus "sudo -u postgres psql -c 'SHOW ssl;'" ssh titania.incus "sudo -u postgres psql -c 'SHOW ssl_cert_file;'" # Test SSL connection externally openssl s_client -connect titania.incus:5432 -starttls postgres ``` ### Casdoor Container Issues ```bash # View container status ssh titania.incus "cd /srv/casdoor && docker compose ps" # View logs ssh titania.incus "cd /srv/casdoor && docker compose logs casdoor" # Restart ssh titania.incus "cd /srv/casdoor && docker compose restart" ``` ### Database Connection ```bash # Connect as postgres admin ssh titania.incus "sudo -u postgres psql" # Connect as casdoor user ssh titania.incus "sudo -u postgres psql -U casdoor -d casdoor -h localhost" # List databases ssh titania.incus "sudo -u postgres psql -c '\l'" # List users ssh titania.incus "sudo -u postgres psql -c '\du'" ``` ### Health Check ```bash # Casdoor health curl -s http://titania.incus:22081/api/health | jq # PostgreSQL accepting connections ssh titania.incus "pg_isready -h localhost" ``` ## Security Considerations 1. **Change default admin password** immediately after deployment 2. **Rotate database passwords** periodically (update vault, redeploy) 3. **Monitor authentication logs** in Grafana (via Alloy/Loki) 4. **SSL certificates** have 10-year validity, regenerate if compromised 5. **Backup PostgreSQL data** regularly - contains all identity data: ```bash ssh titania.incus "sudo -u postgres pg_dump casdoor > casdoor_backup.sql" ``` ## Related Documentation - [Ansible Practices](ansible.md) - Playbook and variable patterns - [Terraform Practices](terraform.md) - S3 bucket provisioning - [OAuth2-Proxy](services/oauth2_proxy.md) - Protecting services with Casdoor SSO