Files
rick-infra/docs/service-integration-guide.md
Joakim b42ee2a22b Fix: Complete authentik Quadlet implementation with networking solution
Resolves authentik deployment issues by implementing proper Podman Quadlet
configuration and fixing networking for external access through Caddy.

Core Fixes:
• Add missing [Install] sections to container Quadlet files for systemd service generation
• Fix pod references from 'systemd-authentik' to 'authentik.pod' for proper Quadlet linking
• Remove problematic --userns=host to use proper rootless user namespaces
• Configure subuid/subgid ranges for authentik user (200000:65536)
• Update networking to bind 0.0.0.0:9000 only (remove unnecessary HTTPS port 9443)
• Add AUTHENTIK_LISTEN__HTTP=0.0.0.0:9000 environment configuration
• Fix Caddy reverse proxy to use HTTP backend instead of HTTPS

Infrastructure Updates:
• Enhance PostgreSQL role with Unix socket configuration and user management
• Improve Valkey role with proper systemd integration and socket permissions
• Add comprehensive service integration documentation
• Update deployment playbooks with backup and restore capabilities

Security Improvements:
• Secure network isolation with Caddy SSL termination
• Reduced attack surface by removing direct HTTPS container exposure
• Proper rootless container configuration with user namespace mapping

Result: authentik now fully operational with external HTTPS access via auth.jnss.me
All systemd services (authentik-pod, authentik-server, authentik-worker) running correctly.
2025-12-04 19:42:31 +01:00

312 lines
8.6 KiB
Markdown

# Service Integration Guide
This guide explains how to add new containerized services to rick-infra with PostgreSQL and Valkey/Redis access via Unix sockets.
## Overview
Rick-infra provides a standardized approach for containerized services to access infrastructure services through Unix sockets, maintaining security while providing optimal performance.
## Architecture Pattern
```
┌─────────────────────────────────────────────────────────────┐
│ Application Service (Podman Container) │
│ │
│ ┌─────────────────┐ │
│ │ Your Container │ │
│ │ UID: service │ (host user namespace) │
│ │ Groups: service,│ │
│ │ postgres, │ (supplementary groups preserved) │
│ │ valkey │ │
│ └─────────────────┘ │
│ │ │
│ └─────────────────────┐ │
└─────────────────────────────────│───────────────────────────┘
┌───────────────▼──────────────┐
│ Host Infrastructure Services │
│ │
│ PostgreSQL Unix Socket │
│ /var/run/postgresql/ │
│ │
│ Valkey Unix Socket │
│ /var/run/valkey/ │
└──────────────────────────────┘
```
## Prerequisites
Your service must be deployed as:
1. **Systemd user service** (via Quadlet)
2. **Dedicated system user**
3. **Podman container** (rootless)
## Step 1: User Setup
Create a dedicated system user for your service and add it to infrastructure groups:
```yaml
- name: Create service user
user:
name: myservice
system: true
shell: /bin/false
home: /opt/myservice
create_home: true
- name: Add service user to infrastructure groups
user:
name: myservice
groups:
- postgres # For PostgreSQL access
- valkey # For Valkey/Redis access
append: true
```
## Step 2: Container Configuration
### Pod Configuration (`myservice.pod`)
```ini
[Unit]
Description=My Service Pod
[Pod]
PublishPort=127.0.0.1:8080:8080
PodmanArgs=--userns=host
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
```
**Key Points**:
- `--userns=host` preserves host user namespace
- Standard port publishing for network access
### Container Configuration (`myservice.container`)
```ini
[Unit]
Description=My Service Container
[Container]
Image=my-service:latest
Pod=myservice.pod
EnvironmentFile=/opt/myservice/.env
User={{ service_uid }}:{{ service_gid }}
Annotation=run.oci.keep_original_groups=1
# Volume mounts for sockets
Volume=/var/run/postgresql:/var/run/postgresql:Z
Volume=/var/run/valkey:/var/run/valkey:Z
# Application volumes
Volume=/opt/myservice/data:/data
Volume=/opt/myservice/logs:/logs
Exec=my-service
[Service]
Restart=always
[Install]
WantedBy=default.target
```
**Key Points**:
- `Annotation=run.oci.keep_original_groups=1` preserves supplementary groups
- Mount socket directories with `:Z` for SELinux relabeling
- Use host UID/GID for the service user
## Step 3: Service Configuration
### PostgreSQL Connection
Use Unix socket connection strings:
```bash
# Environment variable
DATABASE_URL=postgresql://myservice@/myservice_db?host=/var/run/postgresql
# Or separate variables
DB_HOST=/var/run/postgresql
DB_USER=myservice
DB_NAME=myservice_db
# No DB_PORT needed for Unix sockets
```
### Valkey/Redis Connection
**Correct Format** (avoids URL parsing issues):
```bash
# Single URL format (recommended)
CACHE_URL=unix:///var/run/valkey/valkey.sock?db=2&password=your_password
# Alternative format
REDIS_URL=redis://localhost/2?unix_socket_path=/var/run/valkey/valkey.sock
```
**Avoid** separate HOST/DB variables which can cause port parsing issues:
```bash
# DON'T USE - causes parsing problems
REDIS_HOST=unix:///var/run/valkey/valkey.sock
REDIS_DB=2
```
## Step 4: Database Setup
Add database setup tasks to your role:
```yaml
- name: Create application database
postgresql_db:
name: "{{ service_db_name }}"
owner: "{{ service_db_user }}"
encoding: UTF-8
lc_collate: en_US.UTF-8
lc_ctype: en_US.UTF-8
become_user: postgres
- name: Create application database user
postgresql_user:
name: "{{ service_db_user }}"
password: "{{ service_db_password }}"
db: "{{ service_db_name }}"
priv: ALL
become_user: postgres
- name: Grant connect privileges
postgresql_privs:
db: "{{ service_db_name }}"
role: "{{ service_db_user }}"
objs: ALL_IN_SCHEMA
privs: ALL
become_user: postgres
```
## Step 5: Service Role Template
Create an Ansible role using this pattern:
```
myservice/
├── defaults/main.yml
├── handlers/main.yml
├── tasks/
│ ├── main.yml
│ ├── database.yml
│ └── cache.yml
├── templates/
│ ├── myservice.env.j2
│ ├── myservice.pod
│ ├── myservice.container
│ └── myservice.caddy.j2
└── README.md
```
### Example Environment Template
```bash
# My Service Configuration
# Generated by Ansible - DO NOT EDIT
# Database Configuration (Unix Socket)
DATABASE_URL=postgresql://{{ service_db_user }}@/{{ service_db_name }}?host={{ postgresql_unix_socket_directories }}
DB_PASSWORD={{ service_db_password }}
# Cache Configuration (Unix Socket)
CACHE_URL=unix://{{ valkey_unix_socket_path }}?db={{ service_valkey_db }}&password={{ valkey_password }}
# Application Configuration
SECRET_KEY={{ service_secret_key }}
LOG_LEVEL={{ service_log_level }}
BIND_ADDRESS={{ service_bind_address }}:{{ service_port }}
```
## Troubleshooting
### Socket Permission Issues
If you get permission denied errors:
1. **Check group membership**:
```bash
groups myservice
# Should show: myservice postgres valkey
```
2. **Verify container annotations**:
```bash
podman inspect myservice --format='{{.Config.Annotations}}'
# Should include: run.oci.keep_original_groups=1
```
3. **Check socket permissions**:
```bash
ls -la /var/run/postgresql/
ls -la /var/run/valkey/
```
### Connection Issues
1. **Test socket access from host**:
```bash
sudo -u myservice psql -h /var/run/postgresql -U myservice myservice_db
sudo -u myservice redis-cli -s /var/run/valkey/valkey.sock ping
```
2. **Check URL format**:
- Use single `CACHE_URL` instead of separate variables
- Include password in URL if required
- Verify database number is correct
### Container Issues
1. **Check container user**:
```bash
podman exec myservice id
# Should show correct UID and supplementary groups
```
2. **Verify socket mounts**:
```bash
podman exec myservice ls -la /var/run/postgresql/
podman exec myservice ls -la /var/run/valkey/
```
## Best Practices
1. **Security**:
- Use dedicated system users for each service
- Limit group memberships to required infrastructure
- Use vault variables for secrets
2. **Configuration**:
- Use single URL format for Redis connections
- Mount socket directories with appropriate SELinux labels
- Include `run.oci.keep_original_groups=1` annotation
3. **Deployment**:
- Test socket access before container deployment
- Use proper dependency ordering in playbooks
- Include database and cache setup tasks
4. **Monitoring**:
- Monitor socket file permissions
- Check service logs for connection errors
- Verify group memberships after user changes
## Example Integration
See the `authentik` role for a complete example of this pattern:
- **Templates**: `roles/authentik/templates/`
- **Tasks**: `roles/authentik/tasks/`
- **Documentation**: `roles/authentik/README.md`
This provides a working reference implementation for Unix socket integration.