#!/usr/bin/env bash # # Configure OIDC for a single client # # Usage: ./scripts/configure-oidc.sh # # This script: # 1. Creates OIDC provider in Authentik # 2. Installs user_oidc app in Nextcloud # 3. Configures OIDC connection # 4. Enables multiple user backends set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" # Check arguments if [ $# -ne 1 ]; then echo -e "${RED}Error: Client name required${NC}" echo "Usage: $0 " exit 1 fi CLIENT_NAME="$1" # Check environment variables if [ -z "${SOPS_AGE_KEY_FILE:-}" ]; then export SOPS_AGE_KEY_FILE="$PROJECT_ROOT/keys/age-key.txt" fi if [ -z "${HCLOUD_TOKEN:-}" ]; then echo -e "${RED}Error: HCLOUD_TOKEN not set${NC}" exit 1 fi echo -e "${BLUE}Configuring OIDC for ${CLIENT_NAME}...${NC}" cd "$PROJECT_ROOT" # Step 1: Get credentials from secrets echo "Getting credentials from secrets..." TOKEN=$(sops -d "secrets/clients/${CLIENT_NAME}.sops.yaml" | grep authentik_bootstrap_token | awk '{print $2}') if [ -z "$TOKEN" ]; then echo -e "${RED}Error: Could not get Authentik token${NC}" exit 1 fi # Step 2: Create OIDC provider in Authentik echo "Creating OIDC provider in Authentik..." # Create Python script cat > /tmp/create_oidc_${CLIENT_NAME}.py << EOFPYTHON import sys, json, urllib.request base_url, token = "http://localhost:9000", "${TOKEN}" def req(p, m='GET', d=None): r = urllib.request.Request(f"{base_url}{p}", json.dumps(d).encode() if d else None, {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, method=m) try: with urllib.request.urlopen(r, timeout=30) as resp: return resp.status, json.loads(resp.read()) except urllib.error.HTTPError as e: return e.code, json.loads(e.read()) if e.headers.get('Content-Type', '').startswith('application/json') else {'error': e.read().decode()} s, d = req('/api/v3/flows/instances/') auth_flow = next((f['pk'] for f in d.get('results', []) if f.get('slug') == 'default-authorization-flow' or f.get('designation') == 'authorization'), None) inval_flow = next((f['pk'] for f in d.get('results', []) if f.get('slug') == 'default-invalidation-flow' or f.get('designation') == 'invalidation'), None) s, d = req('/api/v3/crypto/certificatekeypairs/') key = d.get('results', [{}])[0].get('pk') if d.get('results') else None if not auth_flow or not key: print(json.dumps({'error': 'Config missing', 'auth_flow': auth_flow, 'key': key}), file=sys.stderr); sys.exit(1) s, prov = req('/api/v3/providers/oauth2/', 'POST', {'name': 'Nextcloud', 'authorization_flow': auth_flow, 'invalidation_flow': inval_flow, 'client_type': 'confidential', 'redirect_uris': [{'matching_mode': 'strict', 'url': 'https://nextcloud.${CLIENT_NAME}.vrije.cloud/apps/user_oidc/code'}], 'signing_key': key, 'sub_mode': 'hashed_user_id', 'include_claims_in_id_token': True}) if s != 201: print(json.dumps({'error': 'Provider failed', 'status': s, 'details': prov}), file=sys.stderr); sys.exit(1) s, app = req('/api/v3/core/applications/', 'POST', {'name': 'Nextcloud', 'slug': 'nextcloud', 'provider': prov['pk'], 'meta_launch_url': 'https://nextcloud.${CLIENT_NAME}.vrije.cloud'}) if s != 201: print(json.dumps({'error': 'App failed', 'status': s, 'details': app}), file=sys.stderr); sys.exit(1) print(json.dumps({'success': True, 'provider_id': prov['pk'], 'application_id': app['pk'], 'client_id': prov['client_id'], 'client_secret': prov['client_secret'], 'discovery_uri': f"https://auth.${CLIENT_NAME}.vrije.cloud/application/o/nextcloud/.well-known/openid-configuration", 'issuer': f"https://auth.${CLIENT_NAME}.vrije.cloud/application/o/nextcloud/"})) EOFPYTHON # Copy script to server and execute cd ansible env HCLOUD_TOKEN="$HCLOUD_TOKEN" \ ansible "${CLIENT_NAME}" \ -i hcloud.yml \ -m copy \ -a "src=/tmp/create_oidc_${CLIENT_NAME}.py dest=/tmp/create_oidc.py mode=0755" \ --private-key "../keys/ssh/${CLIENT_NAME}" > /dev/null 2>&1 # Execute the script OIDC_RESULT=$(env HCLOUD_TOKEN="$HCLOUD_TOKEN" \ ansible "${CLIENT_NAME}" \ -i hcloud.yml \ -m shell \ -a "docker exec -i authentik-server python3 < /tmp/create_oidc.py" \ --private-key "../keys/ssh/${CLIENT_NAME}" 2>/dev/null | grep -A1 "CHANGED" | tail -1) if [ -z "$OIDC_RESULT" ]; then echo -e "${RED}Error: Failed to create OIDC provider${NC}" exit 1 fi # Parse credentials CLIENT_ID=$(echo "$OIDC_RESULT" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['client_id'])") CLIENT_SECRET=$(echo "$OIDC_RESULT" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['client_secret'])") DISCOVERY_URI=$(echo "$OIDC_RESULT" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['discovery_uri'])") if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ] || [ -z "$DISCOVERY_URI" ]; then echo -e "${RED}Error: Failed to parse OIDC credentials${NC}" exit 1 fi echo -e "${GREEN}✓ OIDC provider created${NC}" # Step 3: Install user_oidc app in Nextcloud echo "Installing user_oidc app..." env HCLOUD_TOKEN="$HCLOUD_TOKEN" \ ansible "${CLIENT_NAME}" \ -i hcloud.yml \ -m shell \ -a "docker exec -u www-data nextcloud php occ app:install user_oidc" \ --private-key "../keys/ssh/${CLIENT_NAME}" > /dev/null 2>&1 || true echo -e "${GREEN}✓ user_oidc app installed${NC}" # Step 4: Configure OIDC provider in Nextcloud echo "Configuring OIDC provider..." env HCLOUD_TOKEN="$HCLOUD_TOKEN" \ ansible "${CLIENT_NAME}" \ -i hcloud.yml \ -m shell \ -a "docker exec -u www-data nextcloud php occ user_oidc:provider --clientid=\"${CLIENT_ID}\" --clientsecret=\"${CLIENT_SECRET}\" --discoveryuri=\"${DISCOVERY_URI}\" \"Authentik\"" \ --private-key "../keys/ssh/${CLIENT_NAME}" > /dev/null 2>&1 echo -e "${GREEN}✓ OIDC provider configured${NC}" # Step 5: Configure OIDC settings echo "Configuring OIDC settings..." env HCLOUD_TOKEN="$HCLOUD_TOKEN" \ ansible "${CLIENT_NAME}" \ -i hcloud.yml \ -m shell \ -a "docker exec -u www-data nextcloud php occ config:app:set user_oidc allow_multiple_user_backends --value=1 && docker exec -u www-data nextcloud php occ config:app:set user_oidc auto_provision --value=1 && docker exec -u www-data nextcloud php occ config:app:set user_oidc single_logout --value=0" \ --private-key "../keys/ssh/${CLIENT_NAME}" > /dev/null 2>&1 echo -e "${GREEN}✓ OIDC settings configured${NC}" # Cleanup rm -f /tmp/create_oidc_${CLIENT_NAME}.py echo -e "${GREEN}✓ OIDC configuration complete for ${CLIENT_NAME}${NC}"