Files
ouranos/docs/casdoor.md

542 lines
16 KiB
Markdown

# Casdoor SSO Identity Provider
Casdoor provides Single Sign-On (SSO) authentication for Ouranos 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 `<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`:
```json
{
"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:
```css
<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:
```html
<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:
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