Update Self-Hosting.md and add Immich Access Revisit Plan
This commit is contained in:
@@ -64,3 +64,12 @@
|
|||||||
- Proxmox is for ephemeral/experimental services
|
- Proxmox is for ephemeral/experimental services
|
||||||
- Dell is for always-on base services (DNS, backups, HA)
|
- Dell is for always-on base services (DNS, backups, HA)
|
||||||
- Synology is bulk storage + media
|
- Synology is bulk storage + media
|
||||||
|
|
||||||
|
## Pangolin reverse proxy notes
|
||||||
|
|
||||||
|
- Pangolin runs in Docker, so when exposing a service that is running directly on the VPS host, the backend must be reachable from the container network, not just from the host itself.
|
||||||
|
- For Pangolin public resources that forward to host-level services, use the Docker-to-host reachable IP (`172.17.0.1`) rather than `127.0.0.1`.
|
||||||
|
- `127.0.0.1` inside the Pangolin container refers to the container loopback, not the VPS host loopback.
|
||||||
|
- If a VPS service is bound only to `127.0.0.1:<port>`, Pangolin cannot reach it from the isolated Docker network.
|
||||||
|
- For example, Gitea did not work when forwarded to `127.0.0.1:3000`; removing the `127.0.0.1` bind and exposing the service on a host-reachable interface made it work.
|
||||||
|
- Practical rule: when a reverse proxy lives in Docker but the upstream service lives on the host, confirm both the host IP and the bind address are reachable from the container namespace.
|
||||||
|
|||||||
203
05_Resources/Immich Access Revisit Plan.md
Normal file
203
05_Resources/Immich Access Revisit Plan.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# Immich Access Revisit Plan
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
We migrated several apps from Nginx to Pangolin on the VPS.
|
||||||
|
Pangolin uses Traefik underneath as the reverse proxy.
|
||||||
|
At least one test route, `immich-test-2.frusetik.com`, is publicly reachable over HTTPS through Pangolin/Traefik and works functionally.
|
||||||
|
|
||||||
|
An OWASP ZAP baseline scan against `https://immich-test-2.frusetik.com` showed mostly passive header and hardening findings, not critical exploit findings.
|
||||||
|
|
||||||
|
Main findings:
|
||||||
|
- Strict-Transport-Security header not set
|
||||||
|
- Missing anti-clickjacking header (`X-Frame-Options` or CSP `frame-ancestors`)
|
||||||
|
- `X-Content-Type-Options` missing
|
||||||
|
- Content-Security-Policy not set
|
||||||
|
- Permissions-Policy not set
|
||||||
|
- Cross-Origin-Resource-Policy missing or invalid
|
||||||
|
- `X-Powered-By` leaks implementation details
|
||||||
|
- Minor cache-control warnings
|
||||||
|
- Minor `Content-Type` issue for `favicon.ico`
|
||||||
|
|
||||||
|
Important decision context:
|
||||||
|
- Do **not** start by changing Immich app behavior.
|
||||||
|
- Prefer fixing this at the reverse proxy edge, meaning Traefik under Pangolin.
|
||||||
|
- Pangolin may not expose first-class UI support for response security headers, so implementation may require Traefik middleware directly or a Pangolin-compatible middleware layer.
|
||||||
|
- Goal for the next pass is a practical rollout plan first, not immediate implementation.
|
||||||
|
|
||||||
|
## Recommended implementation plan
|
||||||
|
|
||||||
|
### 1. Prioritize these headers first
|
||||||
|
|
||||||
|
#### Tier 1, high value, low risk
|
||||||
|
Implement these first at the reverse proxy layer:
|
||||||
|
- `Strict-Transport-Security` (HSTS), starting conservatively
|
||||||
|
- `X-Content-Type-Options: nosniff`
|
||||||
|
- Anti-clickjacking protection, preferably `Content-Security-Policy: frame-ancestors 'self'` if supported cleanly, otherwise `X-Frame-Options: SAMEORIGIN`
|
||||||
|
- Remove or overwrite `X-Powered-By` where possible
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- These usually deliver real hardening value
|
||||||
|
- They are unlikely to break a normal Immich deployment when rolled out carefully
|
||||||
|
- They address the clearest ZAP findings first
|
||||||
|
|
||||||
|
#### Tier 2, useful but needs care
|
||||||
|
- `Permissions-Policy`
|
||||||
|
- `Referrer-Policy` (even if ZAP did not call it out, it is often worth setting while touching edge headers)
|
||||||
|
- `Cross-Origin-Resource-Policy`
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- Useful cleanup and defense-in-depth
|
||||||
|
- More likely to have edge-case behavior depending on how the app serves media, embeds resources, or uses browser APIs
|
||||||
|
|
||||||
|
#### Tier 3, highest risk / most annoying
|
||||||
|
- Full `Content-Security-Policy`
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- CSP is valuable, but also the most likely to break Immich UI, API calls, previews, maps, workers, WebSockets, or third-party assets if applied too aggressively
|
||||||
|
- Do not enable a strict CSP early without observing actual resource usage first
|
||||||
|
|
||||||
|
### 2. Where to implement in Pangolin + Traefik
|
||||||
|
|
||||||
|
Target layer: **Traefik response middleware at the edge**, attached only to the Immich router/service first.
|
||||||
|
|
||||||
|
Preferred order of implementation options:
|
||||||
|
1. Pangolin-supported middleware or advanced config, if Pangolin exposes a stable way to attach Traefik headers middleware to a specific route
|
||||||
|
2. Traefik dynamic configuration file/provider mounted into the Traefik container and referenced by the Immich router
|
||||||
|
3. Docker labels on the relevant service/router, if that is how Pangolin composes Traefik config internally and if custom labels are supported safely
|
||||||
|
|
||||||
|
Practical principle:
|
||||||
|
- Start **route-specific**, not global
|
||||||
|
- Prove the header set on `immich-test-2.frusetik.com`
|
||||||
|
- Only later consider promoting a safe subset to a shared middleware for other apps
|
||||||
|
|
||||||
|
### 3. Safe testing sequence after each change
|
||||||
|
|
||||||
|
For every header change:
|
||||||
|
1. Apply one small change or one tightly related bundle
|
||||||
|
2. Reload/redeploy Traefik/Pangolin config
|
||||||
|
3. Validate headers manually:
|
||||||
|
- `curl -I https://immich-test-2.frusetik.com`
|
||||||
|
- browser devtools network tab
|
||||||
|
4. Validate Immich behavior manually:
|
||||||
|
- login page loads
|
||||||
|
- login works
|
||||||
|
- photo thumbnails load
|
||||||
|
- full image/video views load
|
||||||
|
- search/basic navigation works
|
||||||
|
- mobile app or external clients still connect, if relevant
|
||||||
|
5. Re-run OWASP ZAP baseline scan
|
||||||
|
6. Check browser console for CSP, CORP, mixed-content, frame, or MIME warnings
|
||||||
|
7. Keep rollback simple, ideally by removing a single middleware reference or reverting one dynamic-config block
|
||||||
|
|
||||||
|
Recommended rollout bundles:
|
||||||
|
- Bundle A: `X-Content-Type-Options`, clickjacking header, remove `X-Powered-By`
|
||||||
|
- Bundle B: HSTS with conservative settings
|
||||||
|
- Bundle C: Permissions-Policy + optional Referrer-Policy
|
||||||
|
- Bundle D: CORP if still useful after observing actual asset behavior
|
||||||
|
- Bundle E: CSP in report-first or minimal mode
|
||||||
|
|
||||||
|
### 4. Headers that may be risky or annoying to enable immediately
|
||||||
|
|
||||||
|
#### Content-Security-Policy
|
||||||
|
Highest risk.
|
||||||
|
Possible breakage areas:
|
||||||
|
- inline scripts/styles
|
||||||
|
- API endpoints
|
||||||
|
- media blobs
|
||||||
|
- WebSockets
|
||||||
|
- map tiles / third-party origins
|
||||||
|
- workers / object URLs
|
||||||
|
|
||||||
|
Safer approach later:
|
||||||
|
- start with very small policy pieces such as `frame-ancestors 'self'`
|
||||||
|
- or use `Content-Security-Policy-Report-Only` first if feasible
|
||||||
|
|
||||||
|
#### Strict-Transport-Security
|
||||||
|
Useful, but do not jump immediately to a long max-age with `includeSubDomains` and `preload`.
|
||||||
|
Safer start:
|
||||||
|
- modest `max-age`, confirm no HTTP fallback dependencies remain, then increase later
|
||||||
|
|
||||||
|
#### Cross-Origin-Resource-Policy
|
||||||
|
Can interfere with media/resource loading depending on how Immich serves assets and whether anything cross-origin is intentional.
|
||||||
|
Start only after confirming actual request patterns.
|
||||||
|
|
||||||
|
#### Permissions-Policy
|
||||||
|
Usually not dangerous, but easy to over-tighten if Immich or browser features rely on camera, microphone, geolocation, fullscreen, etc.
|
||||||
|
Needs app-aware review.
|
||||||
|
|
||||||
|
### 5. What to inspect before implementation
|
||||||
|
|
||||||
|
Before touching config, inspect these exact areas:
|
||||||
|
|
||||||
|
#### A. Pangolin deployment and compose files
|
||||||
|
Need to locate:
|
||||||
|
- Pangolin `docker-compose.yml` / compose stack
|
||||||
|
- Traefik container definition
|
||||||
|
- mounted config volumes
|
||||||
|
- environment variables controlling Traefik/Pangolin integration
|
||||||
|
- whether Pangolin regenerates or overwrites Traefik config automatically
|
||||||
|
|
||||||
|
Questions to answer:
|
||||||
|
- Is Traefik running as its own container or embedded in the Pangolin stack?
|
||||||
|
- Where does dynamic config live?
|
||||||
|
- What survives container restarts or stack updates?
|
||||||
|
|
||||||
|
#### B. Traefik static and dynamic config
|
||||||
|
Need to inspect:
|
||||||
|
- static Traefik config file (`traefik.yml` / `traefik.toml` / command args)
|
||||||
|
- dynamic config directory or file provider
|
||||||
|
- existing middlewares
|
||||||
|
- existing routers/services for the Immich test route
|
||||||
|
|
||||||
|
Questions to answer:
|
||||||
|
- How is `immich-test-2.frusetik.com` mapped today?
|
||||||
|
- Can a custom headers middleware be attached cleanly to only that router?
|
||||||
|
- Is there already a shared security middleware that should be extended instead of duplicated?
|
||||||
|
|
||||||
|
#### C. Pangolin route/resource definitions
|
||||||
|
Need to inspect:
|
||||||
|
- how Pangolin stores public-resource definitions
|
||||||
|
- whether there is a UI/API/config field for advanced middleware, headers, or Traefik labels
|
||||||
|
- whether Pangolin regenerates routes from an internal database/config API
|
||||||
|
|
||||||
|
Questions to answer:
|
||||||
|
- Is custom middleware supported directly?
|
||||||
|
- If not, what is the least fragile escape hatch?
|
||||||
|
|
||||||
|
#### D. Immich upstream service definition
|
||||||
|
Need to inspect:
|
||||||
|
- compose/service definition for Immich and related containers
|
||||||
|
- whether responses already include any app-set headers
|
||||||
|
- whether WebSockets or special upstream behavior need to be preserved
|
||||||
|
|
||||||
|
Questions to answer:
|
||||||
|
- Are any headers already being set upstream and potentially overwritten?
|
||||||
|
- Does the route use WebSockets, SSE, special caching, or asset paths that constrain proxy hardening?
|
||||||
|
|
||||||
|
#### E. Current public response behavior
|
||||||
|
Capture a baseline before changes:
|
||||||
|
- `curl -I https://immich-test-2.frusetik.com`
|
||||||
|
- full response headers for key pages and asset URLs
|
||||||
|
- browser devtools export or notes for console/network behavior
|
||||||
|
- current ZAP baseline output
|
||||||
|
|
||||||
|
This gives a clean before/after comparison and rollback confidence.
|
||||||
|
|
||||||
|
## Suggested first implementation later
|
||||||
|
|
||||||
|
When revisiting, the safest first real pass is:
|
||||||
|
1. Inspect Pangolin + Traefik config locations and how route-specific middleware is attached
|
||||||
|
2. Add a route-specific middleware for:
|
||||||
|
- `X-Content-Type-Options: nosniff`
|
||||||
|
- clickjacking protection (`frame-ancestors 'self'` or `X-Frame-Options: SAMEORIGIN`)
|
||||||
|
- suppress `X-Powered-By`
|
||||||
|
3. Test manually
|
||||||
|
4. Re-run ZAP baseline
|
||||||
|
5. Add conservative HSTS
|
||||||
|
6. Test again
|
||||||
|
7. Leave CSP for a separate deliberate pass
|
||||||
|
|
||||||
|
## Decision rule
|
||||||
|
|
||||||
|
If Pangolin does not provide a stable, update-safe way to express these headers, prefer a **Traefik dynamic-config middleware** over ad-hoc hacks inside the app container.
|
||||||
Reference in New Issue
Block a user