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
|
||||
- Dell is for always-on base services (DNS, backups, HA)
|
||||
- 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