Replaces the minimal project description with a comprehensive README including a component overview table, quick start instructions, common Ansible operations, and links to detailed documentation. Aligns with Red Panda Approval™ standards.
16 KiB
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
casdoordatabase user only has access to thecasdoordatabase - 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:
- 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:
cd terraform
terraform apply
2. Ansible Vault Secrets
Add to ansible/inventory/group_vars/all/vault.yml:
# 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:
# Database password
openssl rand -base64 24
# Auth state
openssl rand -hex 16
3. Alloy Log Collection
Ensure Alloy is deployed to receive syslog:
ansible-playbook alloy/deploy.yml --limit titania.incus
Deployment
Fresh Installation
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
# 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):
ansible-playbook casdoor/remove.yml
ansible-playbook casdoor/deploy.yml
To completely reset (including database):
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:
# 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:
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:
- Navigate to https://pgadmin.ouranos.helu.ca
- Add Server:
- General tab
- Name:
Titania PostgreSQL (Casdoor)
- Name:
- Connection tab
- Host:
titania.incus - Port:
5432 - Database:
casdoor - Username:
casdoor - Password: (from vault)
- Host:
- SSL tab
- SSL Mode:
Verify-CA - Root certificate:
/var/lib/pgadmin/certs/titania-postgres-ca.crt
- SSL Mode:
- General tab
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 <style> tags) |
footerHtml |
Custom footer HTML | Replaces "Powered by Casdoor" |
formSideHtml |
HTML beside the form | Side panel content |
formBackgroundUrl |
Background image URL | Full-page background |
formBackgroundUrlMobile |
Mobile background image | Mobile background |
signupHtml |
Custom HTML for signup page | Signup page only |
signinHtml |
Custom HTML for signin page | Signin page only |
Configuration via init_data.json
Application branding is configured in ansible/casdoor/init_data.json.j2:
{
"applications": [
{
"name": "app-heluca",
"formCss": "<style>/* Your CSS here */</style>",
"footerHtml": "<div style=\"text-align:center;\">Powered by Helu.ca</div>",
"headerHtml": "<style>/* Additional CSS via style tag */</style>",
"formBackgroundUrl": "https://example.com/bg.jpg"
}
]
}
Example: Custom Theme CSS
The formCss field contains CSS to customize the Ant Design components:
<style>
/* Login panel styling */
.login-panel {
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 0 30px 20px rgba(255,164,21,0.12);
}
/* Primary button colors */
.ant-btn-primary {
background-color: #4b96ff !important;
border-color: #4b96ff !important;
}
.ant-btn-primary:hover {
background-color: #58c0ff !important;
border-color: #58c0ff !important;
}
/* Link colors */
a { color: #ffa415; }
a:hover { color: #ffc219; }
/* Input focus states */
.ant-input:focus, .ant-input-focused {
border-color: #4b96ff !important;
box-shadow: 0 0 0 2px rgba(75,150,255,0.2) !important;
}
/* Checkbox styling */
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #4b96ff !important;
border-color: #4b96ff !important;
}
</style>
Example: Custom Footer
Replace the default "Powered by Casdoor" footer:
<div style="text-align:center;padding:10px;color:#666;">
<a href="https://helu.ca" style="color:#4b96ff;text-decoration:none;">
Powered by Helu.ca
</a>
</div>
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:
- Via Admin UI: Applications → Edit → Update CSS/HTML fields
- Via API: Use Casdoor's REST API to update application settings
- 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:
- Login with default credentials:
admin/123 - Change admin password immediately
- Create organization for your domain
- Create applications for services that need SSO:
- SearXNG (via OAuth2-Proxy)
- Grafana
- Other internal services
OAuth2 Application Setup
For each service:
- Applications → Add
- Configure OAuth2 settings:
- Redirect URI:
https://service.ouranos.helu.ca/oauth2/callback - Grant types: Authorization Code
- Redirect URI:
- Note the Client ID and Client Secret for service configuration
Troubleshooting
PostgreSQL Issues
# 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
# 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
# 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
# Casdoor health
curl -s http://titania.incus:22081/api/health | jq
# PostgreSQL accepting connections
ssh titania.incus "pg_isready -h localhost"
Security Considerations
- Change default admin password immediately after deployment
- Rotate database passwords periodically (update vault, redeploy)
- Monitor authentication logs in Grafana (via Alloy/Loki)
- SSL certificates have 10-year validity, regenerate if compromised
- Backup PostgreSQL data regularly - contains all identity data:
ssh titania.incus "sudo -u postgres pg_dump casdoor > casdoor_backup.sql"
Related Documentation
- Ansible Practices - Playbook and variable patterns
- Terraform Practices - S3 bucket provisioning
- OAuth2-Proxy - Protecting services with Casdoor SSO