Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This replaces the official test installer (`curl -L https://opencloud.eu/install
**Requirements:** Linux VPS, Docker Compose v2, DNS pointing at the server, ports 80/443 open.

```bash
git clone --recurse-submodules https://github.com/your-org/opencloud-easy-deploy.git
git clone --recurse-submodules https://github.com/opencomp-eu/opencloud-easy-deploy.git
cd opencloud-easy-deploy
bash ensure-dependencies.sh # Docker, uv, submodules, Python deps
bash wizard.sh # interactive: writes deploy.yaml and deploys
Expand Down Expand Up @@ -54,7 +54,7 @@ bash apply.sh
See [`deploy.yaml.example`](deploy.yaml.example). Key sections:

- **opencloud** — domain, image tag, persistent `data_dir` / `config_dir` / `apps_dir`
- **proxy** — `caddy` (only option in v1)
- **proxy** — `caddy` with `mode: standalone` (default) or `integrate` (shared Caddy via [easydeploy-engine](../easydeploy-engine))
- **auth** — `builtin` (simple admin login) or `oidc` (external IdP)
- **weboffice** — `euro_office` or `collabora` (mutually exclusive with each other)
- **modules** — optional search, antivirus, radicale, monitoring
Expand All @@ -70,6 +70,8 @@ Uses OpenCloud's built-in LDAP. Admin password is generated on first `apply.sh`

Set `auth.mode: oidc` and configure `auth.oidc` in `deploy.yaml`. The stack adds `idm/external-idp.yml` plus a local overlay for role mapping via `proxy.yaml`.

**Kanidm** (same VPS) uses overlay `overlays/idm/kanidm-provider.yml` instead: default role driver, not OIDC claim mapping. See [`docs/integrating-engine.md`](docs/integrating-engine.md). For a standalone clone, run `bash wizard.sh` here, or let [easydeploy-engine](../easydeploy-engine) wire both kits.

#### Authentik setup

1. **Create groups** for OpenCloud roles:
Expand Down Expand Up @@ -129,7 +131,7 @@ Internet → Caddy (:443, Let's Encrypt)
opencloud-compose stack (docker network: opencloud-net)
├── opencloud
├── euro-office (optional)
├── ldap-server (OIDC mode only)
├── ldap-server (OIDC mode only — OpenCloud's local user/graph store, not the IdP)
└── optional modules (tika, clamav, …)
```

Expand Down Expand Up @@ -312,7 +314,11 @@ If OpenCloud logs show `WopiDiscovery: wopi app url failed with unexpected code

3. **JWT mismatch** — Euro Office `JWT_SECRET` must match OpenCloud `COLLABORATION_WOPI_SECRET` (not `COLLABORATION_JWT_SECRET`, which breaks internal REVA tokens). Both are set from `.opencloud-easy-deploy/secrets.yaml` on apply. If JWT was wrong on first boot, remove `<data-root>/euro-office` and re-apply so Euro Office regenerates its persisted secrets.

4. **X-Frame-Options / iframe blocked** — If the browser console shows Euro Office blocked by `X-Frame-Options: sameorigin`, re-run `bash apply.sh` so Caddy sets `Content-Security-Policy: frame-ancestors` for the Euro Office domain instead.
4. **X-Frame-Options / iframe blocked** — If the browser console shows Euro Office blocked by `X-Frame-Options: sameorigin`, re-run `bash apply.sh` so Caddy sets `Content-Security-Policy: frame-ancestors` for the Euro Office domain instead. Opening a document while OpenCloud itself is iframed (Bulwark) also needs the webmail origin in that list; engine apply writes it from `bulwark.domain`.

5. **OpenCloud `frame-src` blocks Euro Office** — If the browser console shows `frame-src` blocking `https://<euro-office-domain>/hosting/wopi/...`, OpenCloud's CSP is missing the document-server origin. Re-run `bash apply.sh` so `csp.yaml` includes `weboffice.domain`.

6. **Bulwark inline iframe blocked (`frame-ancestors 'self'`)** — OpenCloud refuses to load inside webmail until `embed.frame_ancestors` includes the Bulwark origin. On a same-VPS engine install, re-run `bash apply.sh` in easydeploy-engine so it writes the embed sidecar from `bulwark.domain`. Standalone: set `embed.frame_ancestors: ["https://webmail.example.com"]` in `deploy.yaml` and re-apply.

Euro Office first boot can take **3–5 minutes** (fonts, caches). `apply.sh` waits for WOPI discovery before restarting OpenCloud.

Expand Down
3 changes: 3 additions & 0 deletions apply.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/scripts/lib.sh"
cd "${SCRIPT_DIR}"

clear_parent_python_env
ensure_docker_group_session "$@"

ensure_dependencies="false"
python_args=()

Expand Down
48 changes: 48 additions & 0 deletions config-templates/opencloud/csp.yaml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by opencloud-easy-deploy — allow OpenCloud web UI to reach the external IdP.
directives:
child-src:
- '''self'''
connect-src:
- '''self'''
- 'blob:'
- 'https://{{IDP_DOMAIN}}'
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
- 'https://update.opencloud.eu/'
- 'https://tile.openstreetmap.org/'
default-src:
- '''none'''
font-src:
- '''self'''
frame-ancestors:
- '''self'''{{FRAME_ANCESTORS_EXTRA}}
frame-src:
- '''self'''
- 'blob:'
- 'https://embed.diagrams.net/'
- 'https://{{IDP_DOMAIN}}'
- 'https://{{WEB_OFFICE_DOMAIN}}'
- 'https://docs.opencloud.eu'
img-src:
- '''self'''
- 'data:'
- 'blob:'
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
- 'https://tile.openstreetmap.org/'
- 'https://{{WEB_OFFICE_DOMAIN}}'
manifest-src:
- '''self'''
media-src:
- '''self'''
object-src:
- '''self'''
- 'blob:'
script-src:
- '''self'''
- '''unsafe-inline'''
- 'https://{{IDP_DOMAIN}}'
style-src:
- '''self'''
- '''unsafe-inline'''
worker-src:
- '''self'''
- 'blob:'
34 changes: 23 additions & 11 deletions deploy.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
opencloud:
domain: cloud.example.com
image: opencloudeu/opencloud-rolling
tag: "7.2.0"
tag: "7.5.0"
admin_username: admin
data_dir: /var/lib/opencloud/data
config_dir: /var/lib/opencloud/config
Expand All @@ -13,28 +13,40 @@ opencloud:

proxy:
type: caddy
mode: standalone
integrate:
network: easydeploy-net

# auth.mode: builtin — built-in LDAP admin (simple VPS default)
# auth.mode: oidc — external IdP (Authentik, Keycloak, etc.)
# auth.mode: oidc — Kanidm (same VPS via easydeploy-engine) or another IdP
auth:
mode: builtin
oidc:
issuer_url: https://authentik.example.com/application/o/opencloud/
account_url: https://authentik.example.com/if/user/
domain: authentik.example.com
# provider: kanidm — adds overlays/idm/kanidm-provider.yml when set
issuer_url: https://idm.example.com/oauth2/openid/opencloud
account_url: https://idm.example.com/
domain: idm.example.com
client_id: opencloud
client_scopes: "openid profile email offline_access"
role_claim: groups
role_mapping:
admin: opencloud-admin
user: opencloud-user
guest: opencloud-guest
client_scopes: "openid profile email groups groups_name"
# Kanidm uses the default user role at login (plus OC_ADMIN_USER_ID).
# Authentik / Keycloak still need role_claim + role_mapping:
# role_claim: groups
# role_mapping:
# admin: opencloud-admin
# user: opencloud-user
# guest: opencloud-guest

weboffice:
enabled: true
type: euro_office
domain: eurooffice.example.com

# Origins allowed to iframe OpenCloud (Bulwark webmail). On a same-VPS engine
# install this is filled from bulwark.domain; set it here for standalone or extras.
# embed:
# frame_ancestors:
# - https://webmail.example.com

modules:
search: false
antivirus: false
Expand Down
12 changes: 12 additions & 0 deletions diagnose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ if [[ -n "$OC_DOMAIN" ]]; then
section "Public HTTPS: OpenCloud"
code="$(http_code "https://${OC_DOMAIN}/")"
echo " https://${OC_DOMAIN}/ → HTTP ${code}"
if [[ -n "$EURO_DOMAIN" ]]; then
oc_headers="$(curl -k -sSI "https://${OC_DOMAIN}/" 2>/dev/null || true)"
oc_csp="$(echo "$oc_headers" | grep -i '^content-security-policy:' || true)"
if echo "$oc_csp" | grep -qi "frame-src" && echo "$oc_csp" | grep -qi "$EURO_DOMAIN"; then
success "OpenCloud CSP frame-src allows ${EURO_DOMAIN}"
elif echo "$oc_csp" | grep -qi "frame-src"; then
error "OpenCloud CSP frame-src does not allow ${EURO_DOMAIN} — document editor iframe will be blocked. Re-run apply.sh"
echo " ${oc_csp}"
else
warn "OpenCloud CSP frame-src not found on ${OC_DOMAIN} response"
fi
fi
fi

section "Recent OpenCloud collaboration errors"
Expand Down
54 changes: 54 additions & 0 deletions docs/integrating-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Integrating with easydeploy-engine

Use this when Kanidm already runs on the same VPS behind **easydeploy-engine** on `easydeploy-net`.

## deploy.yaml

```yaml
proxy:
type: caddy
mode: integrate
integrate:
network: easydeploy-net

opencloud:
domain: cloud.example.com
# ...

auth:
mode: oidc
oidc:
provider: kanidm # adds Kanidm-specific compose overlay
issuer_url: https://idm.example.com/oauth2/openid/opencloud
account_url: https://idm.example.com/
domain: idm.example.com
client_id: opencloud
client_scopes: openid profile email groups groups_name
```

Kanidm uses a **per-client** issuer (`/oauth2/openid/<client_id>`), not the portal origin. Role assignment uses the default `user` role at login (`PROXY_ROLE_ASSIGNMENT_DRIVER=default`); put the operator in Kanidm group `opencloud-admin` so they become `OC_ADMIN_USER_ID`. Do not rely on `role_claim` / `opencloudRoles` for Kanidm.

## Kanidm OIDC client

OpenCloud's **browser** login uses a **public** OIDC client with PKCE (no client secret). The client ID must match `auth.oidc.client_id` in OpenCloud (`opencloud` above).

On a same-VPS engine install you can skip registering the client by hand: `bash wizard.sh` in easydeploy-engine clones this repo if needed and writes the Kanidm OIDC sidecar. Kanidm apply then creates the public client and default groups (`opencloud-admin`, `opencloud-user`, `opencloud-guest`).

Give your user the `opencloud-admin` group in Kanidm, not by creating a local OpenCloud account. OpenCloud's bundled OpenLDAP is only the local graph store; wipe it with `bash apply.sh --wipe-local-accounts` if a failed first login left a conflicting user (`/access-denied` after a successful Kanidm grant).

## Apply order

1. Configure Kanidm and OpenCloud, then enable both in `engine.yaml`.
2. Run `bash apply.sh` in easydeploy-engine.
3. The engine writes both OIDC sidecars, applies Kanidm to register the
`opencloud` client, applies OpenCloud to consume the provider configuration,
and reloads shared Caddy.

Do not use `--skip-kits` for the initial identity wiring: that writes sidecars
but does not register the client or restart OpenCloud.

Standalone OpenCloud Caddy (`opencloud_caddy`) is not started in integrate mode.

## Bulwark inline iframe

When Stalwart/Bulwark is enabled on the same engine, apply writes `.opencloud-easy-deploy/integration/embed.yaml` with the webmail origin. OpenCloud then allows that origin in CSP `frame-ancestors`, and the document editor (Euro Office / Collabora) allows it too so nested iframes work. To add more parents, set `embed.frame_ancestors` in `deploy.yaml`. Set `embed.managed: false` to ignore the engine sidecar.
2 changes: 1 addition & 1 deletion easydeploy-lib
32 changes: 32 additions & 0 deletions overlays/idm/kanidm-provider.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
# Kanidm OIDC (public PKCE client + preferred_username + groups).
services:
opencloud:
environment:
PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD: "none"
PROXY_OIDC_REWRITE_WELLKNOWN: "true"
PROXY_AUTOPROVISION_ACCOUNTS: "true"
PROXY_USER_OIDC_CLAIM: "preferred_username"
PROXY_USER_CS3_CLAIM: "username"
PROXY_AUTOPROVISION_CLAIM_USERNAME: "preferred_username"
PROXY_AUTOPROVISION_CLAIM_EMAIL: "email"
PROXY_AUTOPROVISION_CLAIM_DISPLAYNAME: "name"
PROXY_AUTOPROVISION_CLAIM_GROUPS: "groups"
# Kanidm custom claims (opencloudRoles) often never reach the access token
# or UserInfo. The oidc driver then lands on /access-denied. Assign the
# built-in user role at login; OC_ADMIN_USER_ID still promotes the operator.
PROXY_ROLE_ASSIGNMENT_DRIVER: "default"
GRAPH_ASSIGN_DEFAULT_USER_ROLE: "true"
SETTINGS_SETUP_DEFAULT_ASSIGNMENTS: "true"
OC_ADMIN_USER_ID: ${OC_ADMIN_USER_ID:-}
OC_LDAP_DISABLE_USER_MECHANISM: "none"
OC_LDAP_URI: ${OC_LDAP_URI:-ldaps://ldap-server:1636}
WEBFINGER_WEB_OIDC_CLIENT_ID: ${OC_OIDC_CLIENT_ID}
WEBFINGER_WEB_OIDC_CLIENT_SCOPES: "openid profile email groups groups_name"
WEB_OIDC_SCOPE: "openid profile email groups groups_name"
WEBFINGER_ANDROID_OIDC_CLIENT_ID: ${WEBFINGER_ANDROID_OIDC_CLIENT_ID:-opencloud-android}
WEBFINGER_ANDROID_OIDC_CLIENT_SCOPES: "openid profile email groups groups_name offline_access"
WEBFINGER_IOS_OIDC_CLIENT_ID: ${WEBFINGER_IOS_OIDC_CLIENT_ID:-opencloud-ios}
WEBFINGER_IOS_OIDC_CLIENT_SCOPES: "openid profile email groups groups_name offline_access"
WEBFINGER_DESKTOP_OIDC_CLIENT_ID: ${WEBFINGER_DESKTOP_OIDC_CLIENT_ID:-opencloud-desktop}
WEBFINGER_DESKTOP_OIDC_CLIENT_SCOPES: "openid profile email groups groups_name offline_access"
2 changes: 2 additions & 0 deletions overlays/idm/oidc-external.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ services:
PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD: "none"
volumes:
- ${OC_CONFIG_DIR}/proxy.yaml:/etc/opencloud/proxy.yaml
- ${OC_CONFIG_DIR}/csp.yaml:/etc/opencloud/csp.yaml

networks:
opencloud-net:
external: true
name: opencloud-net
Loading
Loading