docs: rewrite README with structured overview and quick start guide
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.
This commit is contained in:
542
docs/casdoor.md
Normal file
542
docs/casdoor.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# 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 `<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
|
||||
Reference in New Issue
Block a user