second commit
This commit is contained in:
99
app/main.py
Normal file
99
app/main.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""
|
||||
FastAPI-Einstiegspunkt. Bindet Security-Header, Router und Static/Template-
|
||||
Auslieferung zusammen (siehe Konzept 6.6 Web-Anwendungs-Hardening).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from app.admin.routes import router as admin_router
|
||||
from app.auth.deps import get_current_user
|
||||
from app.auth.routes import router as auth_router
|
||||
from app.catalog.routes import router as catalog_router
|
||||
from app.db import close_db, init_db
|
||||
from app.rdp_proxy.ws_tunnel import router as rdp_ws_router
|
||||
from app.ssh_proxy.sftp import router as sftp_router
|
||||
from app.ssh_proxy.terminal_ws import router as ssh_ws_router
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
await init_db()
|
||||
yield
|
||||
await close_db()
|
||||
|
||||
|
||||
app = FastAPI(title="Jumphost Gateway", lifespan=lifespan, docs_url=None, redoc_url=None, openapi_url=None)
|
||||
|
||||
app.include_router(auth_router)
|
||||
app.include_router(admin_router)
|
||||
app.include_router(catalog_router)
|
||||
app.include_router(ssh_ws_router)
|
||||
app.include_router(sftp_router)
|
||||
app.include_router(rdp_ws_router)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def security_headers_middleware(request: Request, call_next):
|
||||
"""Setzt die in Konzept 6.6 geforderten Security-Header auf jede Antwort.
|
||||
Laeuft unabhaengig davon, ob nginx vorgeschaltet ist (Defense-in-Depth --
|
||||
nginx setzt in der Praxis dieselben Header zusaetzlich, siehe ansible/roles/nginx_proxy)."""
|
||||
response = await call_next(request)
|
||||
response.headers["X-Frame-Options"] = "DENY"
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
response.headers["Referrer-Policy"] = "no-referrer"
|
||||
response.headers["Permissions-Policy"] = "clipboard-read=(self), clipboard-write=(self), fullscreen=(self)"
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self'; "
|
||||
"style-src 'self'; "
|
||||
"img-src 'self' data:; "
|
||||
"connect-src 'self' ws: wss:; "
|
||||
"frame-ancestors 'none'; "
|
||||
"base-uri 'self'; "
|
||||
"form-action 'self'"
|
||||
)
|
||||
response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains"
|
||||
return response
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request):
|
||||
# Hinweis: seit Starlette >=1.x ist "TemplateResponse(request, name, ...)"
|
||||
# die aktuelle Aufrufkonvention (die alte "TemplateResponse(name, {...})"
|
||||
# wurde entfernt) -- beim Dependency-Upgrade im Rahmen der pip-audit-
|
||||
# Bereinigung angepasst und per Pentest-Testsuite regressionsgetestet.
|
||||
return templates.TemplateResponse(request, "login.html", {})
|
||||
|
||||
|
||||
@app.get("/dashboard", response_class=HTMLResponse)
|
||||
async def dashboard(request: Request):
|
||||
# Auth-Pruefung erfolgt clientseitig ueber GET /auth/me (401 -> Redirect zu /);
|
||||
# serverseitig zusaetzlich abgesichert, sobald Templates dynamische Inhalte rendern.
|
||||
return templates.TemplateResponse(request, "dashboard.html", {})
|
||||
|
||||
|
||||
@app.get("/terminal/{host_id}", response_class=HTMLResponse)
|
||||
async def terminal_page(request: Request, host_id: int):
|
||||
return templates.TemplateResponse(request, "terminal.html", {"host_id": host_id})
|
||||
|
||||
|
||||
@app.get("/rdp/{host_id}", response_class=HTMLResponse)
|
||||
async def rdp_page(request: Request, host_id: int):
|
||||
return templates.TemplateResponse(request, "rdp.html", {"host_id": host_id})
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
async def healthz():
|
||||
return {"status": "ok"}
|
||||
Reference in New Issue
Block a user