diff --git a/docs/installation/advanced/ip-access-restriction.md b/docs/installation/advanced/ip-access-restriction.md new file mode 100644 index 00000000..392423d5 --- /dev/null +++ b/docs/installation/advanced/ip-access-restriction.md @@ -0,0 +1,222 @@ +--- +description: Restrict access to your SeaTable Server to selected IP addresses (allowlist) or block single IP addresses (blocklist) with Caddy, while Let's Encrypt keeps working. +--- + +# IP Access Restriction + +By default, your SeaTable Server is accessible from any IP address. This article explains how to limit access to a list of allowed IP addresses (allowlist) or how to block single IP addresses (blocklist). Requests from all other IP addresses are answered with `403 Forbidden`. + +The restriction is done by Caddy. You don't have to modify `seatable-server.yml` or `caddy.yml`. Instead, you add one additional yml file and one file containing your IP addresses. This keeps your changes intact during updates. + +!!! warning "Public features are restricted too" + + The restriction applies to every request. Users with a non-allowed IP address can't use any features that are meant to be public, for example: + + - Forms (`/dtable/forms/...`) + - External links and shared views + - Universal Apps + - API requests from external services like n8n Cloud, Make or Zapier + + Make sure to add the IP addresses of all users and services that need access. + +## Configure an allowlist + +### Step 1: Create the file with your allowed IP addresses + +Create a new directory `caddy-snippets` inside `/opt/seatable-compose` and add a file `ip-allowlist.caddy`: + +```bash +mkdir -p /opt/seatable-compose/caddy-snippets +nano /opt/seatable-compose/caddy-snippets/ip-allowlist.caddy +``` + +Insert the following content and replace the example IP addresses with your own: + +```Caddyfile +@blocked { + # Office + not remote_ip 203.0.113.10 + # VPN gateway + not remote_ip 198.51.100.0/24 + # Home office (multiple addresses can be separated by spaces) + not remote_ip 192.0.2.44 192.0.2.45 + # Docker networks, required for components on the same host + not remote_ip private_ranges +} +handle @blocked { + respond 403 +} +``` + +**Explanation** + +- Every line `not remote_ip ...` accepts one or more IP addresses or CIDR ranges, separated by spaces. +- All lines must match for a request to be blocked. In other words: a request is blocked if its IP address is not part of any line. +- Lines starting with `#` are comments. +- `private_ranges` covers all private and loopback networks (e.g. `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`). Components like Collabora Online, OnlyOffice, SeaDoc or the Python Pipeline call your SeaTable Server via its public URL. These requests reach Caddy with an internal Docker IP address and would be blocked without this line. + +!!! warning "Use handle, not respond" + + You might find examples that only use `respond @blocked 403`. Don't use this, it leaves gaps. Caddy executes `route` blocks before `respond`, and some components add routes that forward requests directly to another container. For example, [`seatable-html-server.yml`](../components/html-server.md) adds such a route for `/app-server/*`. With `respond`, this path stays accessible for every IP address. `handle` is executed before `route` and therefore blocks these paths as well. + +!!! note "Server in a private network" + + If your server is located in a private network (e.g. your company LAN), `private_ranges` allows every client from this network. In this case, replace `private_ranges` with the Docker networks only (usually `172.16.0.0/12`) and add the allowed LAN addresses explicitly. + +### Step 2: Create `ip-allowlist.yml` + +Create a new file `/opt/seatable-compose/ip-allowlist.yml` with the following content: + +```yaml +--- +services: + caddy: + volumes: + - ./caddy-snippets:/etc/caddy/custom:ro + + seatable-server: + labels: + caddy_0.import: /etc/caddy/custom/ip-allowlist.caddy +``` + +This mounts the directory `caddy-snippets` into the Caddy container and imports your IP list into the configuration of SeaTable Server. Docker Compose merges these settings with `caddy.yml` and `seatable-server.yml`. + +!!! tip "Mount the directory, not the file" + + Please mount the directory and not only the file. Many editors (like vim) replace a file when saving it. A container with a single-file mount would continue to see the old version of your file. + +### Step 3: Add `ip-allowlist.yml` to your `.env` file + +`ip-allowlist.yml` must be the **last** entry of the `COMPOSE_FILE` variable inside your `.env` file: + +```bash +sed -i "s/COMPOSE_FILE='\(.*\)'/COMPOSE_FILE='\1,ip-allowlist.yml'/" /opt/seatable-compose/.env +``` + +### Step 4: Apply the changes + +Run the following command inside `/opt/seatable-compose`: + +```bash +docker compose up -d +``` + +Check the logs of Caddy for errors: + +```bash +docker logs caddy 2>&1 | grep -i "invalid block" +``` + +!!! danger "A syntax error disables SeaTable" + + If your `ip-allowlist.caddy` contains a syntax error, Caddy removes the complete configuration of your SeaTable Server, and SeaTable is no longer accessible. The logs show a message like `Removing invalid block: parsing caddyfile tokens for 'handle' ...`. Fix your file and apply the changes again. + +## Test the restriction + +Run the following commands from a computer with a non-allowed IP address (e.g. a smartphone hotspot). Replace `seatable.example.com` with your domain: + +```bash +curl -s -o /dev/null -w '%{http_code}\n' https://seatable.example.com/ +curl -s -o /dev/null -w '%{http_code}\n' https://seatable.example.com/api2/ping/ +``` + +Both commands must return `403`. From an allowed IP address, you get `302` and `200`, and SeaTable works as usual. + +## Restrict additional components + +Components like Collabora Online or n8n are accessible via their own ports. Each component has its own Caddy configuration, which is not covered by the restriction of SeaTable Server. If you want to restrict these components as well, add their services to `ip-allowlist.yml`. Please note that these services use the label prefix `caddy` instead of `caddy_0`: + +```yaml +--- +services: + caddy: + volumes: + - ./caddy-snippets:/etc/caddy/custom:ro + + seatable-server: + labels: + caddy_0.import: /etc/caddy/custom/ip-allowlist.caddy + + collabora: + labels: + caddy.import: /etc/caddy/custom/ip-allowlist.caddy + + n8n: + labels: + caddy.import: /etc/caddy/custom/ip-allowlist.caddy +``` + +These are the service names of the components with their own ports: + +| Component | yml file | Service name | Default port | +| ---------------- | ----------------- | --------------- | ------------ | +| Gatus | `gatus.yml` | `gatus` | 6220 | +| Uptime Kuma | `uptime-kuma.yml` | `uptime-kuma` | 6230 | +| n8n | `n8n.yml` | `n8n` | 6231 | +| Collabora Online | `collabora.yml` | `collabora` | 6232 | +| OnlyOffice | `onlyoffice.yml` | `onlyoffice` | 6233 | +| Zabbix | `zabbix.yml` | `zabbix-web` | 6235 | +| tldraw | `tldraw.yml` | `tldraw-worker` | 6239 | +| SeaDoc | `seadoc.yml` | `seadoc` | 6240 | +| Dozzle | `dozzle.yml` | `dozzle` | 6241 | + +!!! warning "Only add services you actually use" + + Only add services whose yml file is part of `COMPOSE_FILE`. Otherwise `docker compose` stops with an error like `service "collabora" has neither an image nor a build context specified`. + +[`gatus.yml`](../components/gatus.md) adds a redirect from `/status` to the Gatus port. This redirect is executed before the IP check and therefore stays accessible for every IP address. Add the import to the `gatus` service to protect the status page itself. + +## Update the list of IP addresses + +Caddy does not notice changes to `ip-allowlist.caddy` automatically. After you have changed the file, reload the configuration of Caddy: + +```bash +docker exec caddy caddy reload --config /config/caddy/Caddyfile.autosave --adapter caddyfile +``` + +The reload applies the new list without any interruption. If your file contains an error, the reload is aborted with an error message and the previous configuration stays active. + +Alternatively, you can restart Caddy with `docker compose restart caddy`. This takes a few seconds, your certificates are kept. + +## Let's Encrypt + +Let's Encrypt keeps working without any further configuration. Caddy answers the ACME challenges for your certificates before your IP check is executed: + +- HTTP-01 challenges on port 80 are answered internally by Caddy. The restriction is only added to the HTTPS configuration. +- TLS-ALPN-01 challenges on port 443 are answered during the TLS handshake, before any HTTP rule is evaluated. + +There is no need to open the restriction for the IP addresses of Let's Encrypt. + +## IPv6 + +If you only allow IPv4 addresses, make sure your domain has no AAAA record. You can check this with: + +```bash +dig AAAA seatable.example.com +short +``` + +If the command returns an IPv6 address, users with IPv4 and IPv6 connect via IPv6. Caddy sees their IPv6 address, which is not on your list, and blocks them. Either remove the AAAA record or add the IPv6 addresses or ranges of your users to `ip-allowlist.caddy`. + +If you use IPv6, IPv6 must be enabled for the Docker network of Caddy. Otherwise, Caddy only sees the IP address of the Docker gateway for every IPv6 request. Read more about this in [Activate IPv6](./ipv6-support.md). + +## Configure a blocklist + +You can also use the opposite approach and block single IP addresses, while all other IP addresses keep access. Remove the `not` from your `ip-allowlist.caddy`: + +```Caddyfile +@blocked { + # Scanner + remote_ip 203.0.113.10 203.0.113.11 + # Network of a hosting provider + remote_ip 198.51.100.0/24 +} +handle @blocked { + respond 403 +} +``` + +In this case, a request is blocked if its IP address matches **any** of the lines. All other steps are identical. + +!!! note "A blocklist offers limited protection" + + A blocklist only blocks IP addresses you already know. Every other IP address keeps access, including requests via IPv6 if your domain has an AAAA record. If you need to protect your server against brute-force attacks, consider tools like fail2ban or CrowdSec. diff --git a/docs/installation/advanced/maintenance-mode.md b/docs/installation/advanced/maintenance-mode.md index 983b7e0d..bfa58c10 100644 --- a/docs/installation/advanced/maintenance-mode.md +++ b/docs/installation/advanced/maintenance-mode.md @@ -11,23 +11,30 @@ Sometimes updates or changes in the configuration are necessary, and it's import Here's how to configure such a maintenance page using Caddy: 1. Go to `/opt/seatable-compose/` -2. Create a copy of your `seatable-server.yml` and name it `maintenance.yml` -3. Replace the current labels of your SeaTable Server with the following labels. -4. Replace `` with one IP address, that should have access to your server. -5. Open your `.env` file and replace `seatable-server.yml` with `maintenance.yml` (in the variable `COMPOSE_FILE`) -6. Run `docker compose up -d` +2. Create a new file `maintenance.yml` with the following content. +3. Replace `` with the IP address that should have access to your server. Multiple IP addresses can be separated by spaces. +4. Open your `.env` file and add `maintenance.yml` as the **last** entry of the variable `COMPOSE_FILE`. +5. Run `docker compose up -d` ```yaml -... +--- +services: + seatable-server: labels: - caddy: ${SEATABLE_SERVER_PROTOCOL:-https}://${SEATABLE_SERVER_HOSTNAME:?Variable is not set or empty} - caddy.@blocked: 'not remote_ip private_ranges' - caddy.respond: '@blocked "SeaTable Cloud is currently undergoing maintenance. The service will be restored shortly. Thank you for your patience." 503' - caddy.header.Retry-After: 3600 - caddy.reverse_proxy: "{{upstreams 80}}" -... + caddy_0.@maintenance: "not remote_ip private_ranges" + caddy_0.handle: "@maintenance" + caddy_0.handle.header: "Retry-After 3600" + caddy_0.handle.respond: '"This SeaTable Server is currently undergoing maintenance. The service will be restored shortly. Thank you for your patience." 503' ``` +Docker Compose merges these labels with the labels of `seatable-server.yml`, so all other settings like the security headers stay active. + +`private_ranges` ensures that components on the same host, like Collabora Online or the Python Pipeline, can still reach your SeaTable Server. + +!!! warning "Use handle, not respond" + + Older versions of this article used `respond` without `handle`. With this configuration, paths that are routed to other containers (e.g. `/app-server/*` of the [HTML server](../components/html-server.md)) stayed accessible during the maintenance. `handle` blocks these paths as well. Read more in [IP Access Restriction](./ip-access-restriction.md). + ## How does maintenance look like If you are accessing your system from an IP address that has been specified in your labels, you can continue using SeaTable as usual. @@ -38,10 +45,10 @@ All other users will see a maintenance page displaying the following message: ## Disable Maintenance Mode -To disable maintenance mode, update your `.env` file by replacing `maintenance.yml` with `seatable-server.yml`. Then, run the command: +To disable maintenance mode, remove `maintenance.yml` from the variable `COMPOSE_FILE` in your `.env` file. Then, run the command: ```bash -docker-compose up -d +docker compose up -d ``` Your SeaTable server will once again be accessible to all users. diff --git a/docs/installation/advanced/webserver-security.md b/docs/installation/advanced/webserver-security.md index 3f7b9d03..7ebd58b1 100644 --- a/docs/installation/advanced/webserver-security.md +++ b/docs/installation/advanced/webserver-security.md @@ -48,3 +48,7 @@ Create a custom copy of your `seatable-server.yml` file and modify these setting ## DNSSEC It also requires DNSSEC from your domain hoster to get the best security measures. + +## IP access restriction + +If you want to limit access to your SeaTable Server to selected IP addresses, read [IP Access Restriction](./ip-access-restriction.md). diff --git a/mkdocs.yml b/mkdocs.yml index 24ed99db..122a8dd5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -189,6 +189,7 @@ nav: - Additional Subdomains: installation/advanced/additional-subdomains.md - Air Gap Installation: installation/advanced/air-gap-installation.md - Webserver Security: installation/advanced/webserver-security.md + - IP Access Restriction: installation/advanced/ip-access-restriction.md - Maintenance Mode: installation/advanced/maintenance-mode.md - Advanced Settings for Caddy: installation/advanced/settings-caddy.md - SeaTable AI: