Xiber NetOS — Public Hostname & SSO
Cloudflare Tunnel deployment with Microsoft Entra ID single sign-on.
Overview
NetOS can be exposed publicly through a Cloudflare Tunnel and protected with Microsoft Entra ID SSO. The auth layer sits in front of the app — unauthenticated users never reach the API or web UI.
Internet
→ Cloudflare Tunnel (netos.xiberian.net)
→ OAuth2-Proxy (Entra OIDC)
→ Nginx (route /api → FastAPI, / → Next.js)
→ API + Web containersTarget Hostname
https://netos.xiberian.netThe hostname routes to the sso-proxy container, not directly to web or API.
Runtime Components
| Container | Role | Port |
|---|---|---|
cloudflared | Outbound tunnel to Cloudflare edge | — |
sso-proxy | OAuth2-Proxy with Entra OIDC | 4180 |
public-proxy | Nginx reverse proxy | 8080 |
web | Next.js UI | 3000 |
api | FastAPI backend | 8000 |
postgres | PostgreSQL 16 | 5432 |
redis | Redis 7 | 6379 |
Routing Rules (Nginx)
| Path | Destination |
|---|---|
/ | Web app (Next.js) |
/api/ | FastAPI backend |
/docs-api/ | FastAPI Swagger UI |
/openapi.json | OpenAPI schema |
Prerequisites
- Cloudflare account with Zero Trust enabled
- Microsoft Entra ID tenant (Xiber:
4944843d-e347-4fe7-a279-4006ce5efc33) - Docker Engine 24+ with Docker Compose v2
Setup Steps
1. Create Entra App Registration
- Go to Azure Portal → App registrations → New registration
- Name:
Xiber NetOS - Redirect URI (Web):
https://netos.xiberian.net/oauth2/callback - Note the Application (client) ID and create a client secret
Recommended access policy:
- Assign only Xiber users or a dedicated
NetOS-Userssecurity group - Require MFA and compliant device if available
- Map Entra groups to NetOS roles (exec, finance, network_eng, etc.)
2. Create Environment File
Copy the example and fill in real values:
cp infra/docker/.env.public.example infra/docker/.env.publicRequired variables:
| Variable | Description | Example |
|---|---|---|
ENTRA_OIDC_ISSUER_URL | Entra OIDC issuer | https://login.microsoftonline.com/{tenant_id}/v2.0 |
ENTRA_CLIENT_ID | App registration client ID | 29833a06-d27e-... |
ENTRA_CLIENT_SECRET | App registration client secret | b-V8Q~bO_YRL... |
OAUTH2_PROXY_COOKIE_SECRET | Random 32-byte base64 string | (generate below) |
CLOUDFLARE_TUNNEL_TOKEN | Tunnel token from Cloudflare dashboard | eyJhIjoi... |
Generate cookie secret:
python3 -c "import base64, secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"3. Create Cloudflare Tunnel
- Go to Cloudflare Zero Trust → Tunnels → Create a tunnel
- Name:
netos - Copy the tunnel token into
.env.publicasCLOUDFLARE_TUNNEL_TOKEN - Add a public hostname:
- Hostname:
netos.xiberian.net - Service:
http://sso-proxy:4180
- Optionally add a Cloudflare Access policy (Microsoft Entra ID, Xiber users only) as a second enforcement layer
4. Start the Public Stack
cd infra/docker
docker compose --env-file .env.public \
-f docker-compose.yml \
-f docker-compose.public.yml \
--profile public \
up -d5. Verify
Check containers:
docker compose -f docker-compose.yml \
-f docker-compose.public.yml \
--profile public \
psOpen in browser:
https://netos.xiberian.netYou should be redirected to Microsoft login. After authenticating, you'll see the NetOS UI.
Architecture Notes
Why OAuth2-Proxy + Cloudflare Access?
Both layers serve complementary purposes:
| Layer | Purpose |
|---|---|
| Cloudflare Access | Edge enforcement — blocks unauthenticated traffic before it reaches your server |
| OAuth2-Proxy | Origin enforcement — protects the app even if tunnel routing is misconfigured |
Keep both. The overhead is negligible and the defense-in-depth is worth it.
JWT Validation (Future)
The current API uses a header-based dev auth shim. Production should:
- Extract the JWT from the OAuth2-Proxy
Authorizationheader or cookie - Validate signature against Entra JWKS endpoint
- Extract user email and group claims
- Map Entra groups to NetOS roles
- Reject requests with invalid/expired tokens
This is tracked in Roadmap → Authentication.
Security Checklist
| Item | Status |
|---|---|
| HTTPS via Cloudflare | Automatic with tunnel |
| Entra OIDC authentication | Via OAuth2-Proxy |
| Cloudflare Access policy | Recommended additional layer |
| Cookie secret rotation | Manual — regenerate and restart periodically |
| Client secret rotation | Via Azure Portal — update .env.public after rotation |
| MFA enforcement | Configure in Entra Conditional Access |
| Role-based access | Dev shim now, JWT group mapping planned |
Troubleshooting
| Issue | Solution |
|---|---|
| Redirect loop after login | Check OAUTH2_PROXY_COOKIE_SECRET is exactly 32 bytes base64-encoded |
| 502 Bad Gateway | Verify sso-proxy and public-proxy containers are running |
| Tunnel not connecting | Check CLOUDFLARE_TUNNEL_TOKEN is correct; verify tunnel is active in CF dashboard |
| CORS errors in browser | Verify Nginx config allows the public hostname as an origin |
| "Access Denied" after login | User may not be in the assigned Entra group; check app assignment |
| API returns 401 | JWT validation not yet implemented — ensure dev auth headers are passed through proxy |
