api: replace django sentry proxy with go proxy to prevent login issues
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
6da78b8c32
commit
0d02dbf55c
|
@ -1,19 +0,0 @@
|
||||||
"""API tasks"""
|
|
||||||
|
|
||||||
from authentik.lib.utils.http import get_http_session
|
|
||||||
from authentik.root.celery import CELERY_APP
|
|
||||||
|
|
||||||
SENTRY_SESSION = get_http_session()
|
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
|
||||||
def sentry_proxy(payload: str):
|
|
||||||
"""Relay data to sentry"""
|
|
||||||
SENTRY_SESSION.post(
|
|
||||||
"https://sentry.beryju.org/api/8/envelope/",
|
|
||||||
data=payload,
|
|
||||||
headers={
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
},
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
|
@ -1,65 +0,0 @@
|
||||||
"""Sentry tunnel"""
|
|
||||||
from json import loads
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http.request import HttpRequest
|
|
||||||
from django.http.response import HttpResponse
|
|
||||||
from rest_framework.authentication import SessionAuthentication
|
|
||||||
from rest_framework.parsers import BaseParser
|
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from rest_framework.request import Request
|
|
||||||
from rest_framework.throttling import AnonRateThrottle
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
from authentik.api.tasks import sentry_proxy
|
|
||||||
from authentik.lib.config import CONFIG
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
class PlainTextParser(BaseParser):
|
|
||||||
"""Plain text parser."""
|
|
||||||
|
|
||||||
media_type = "text/plain"
|
|
||||||
|
|
||||||
def parse(self, stream, media_type=None, parser_context=None) -> str:
|
|
||||||
"""Simply return a string representing the body of the request."""
|
|
||||||
return stream.read()
|
|
||||||
|
|
||||||
|
|
||||||
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
|
||||||
"""CSRF-exempt Session authentication"""
|
|
||||||
|
|
||||||
def enforce_csrf(self, request: Request):
|
|
||||||
return # To not perform the csrf check previously happening
|
|
||||||
|
|
||||||
|
|
||||||
class SentryTunnelView(APIView):
|
|
||||||
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
|
|
||||||
|
|
||||||
serializer_class = None
|
|
||||||
parser_classes = [PlainTextParser]
|
|
||||||
throttle_classes = [AnonRateThrottle]
|
|
||||||
permission_classes = [AllowAny]
|
|
||||||
authentication_classes = [CsrfExemptSessionAuthentication]
|
|
||||||
|
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
|
|
||||||
# Only allow usage of this endpoint when error reporting is enabled
|
|
||||||
if not CONFIG.y_bool("error_reporting.enabled", False):
|
|
||||||
LOGGER.debug("error reporting disabled")
|
|
||||||
return HttpResponse(status=400)
|
|
||||||
# Body is 2 json objects separated by \n
|
|
||||||
full_body = request.body
|
|
||||||
lines = full_body.splitlines()
|
|
||||||
if len(lines) < 1:
|
|
||||||
return HttpResponse(status=400)
|
|
||||||
header = loads(lines[0])
|
|
||||||
# Check that the DSN is what we expect
|
|
||||||
dsn = header.get("dsn", "")
|
|
||||||
if dsn != settings.SENTRY_DSN:
|
|
||||||
LOGGER.debug("Invalid dsn", have=dsn, expected=settings.SENTRY_DSN)
|
|
||||||
return HttpResponse(status=400)
|
|
||||||
sentry_proxy.delay(full_body.decode())
|
|
||||||
return HttpResponse(status=204)
|
|
|
@ -11,7 +11,6 @@ from authentik.admin.api.tasks import TaskViewSet
|
||||||
from authentik.admin.api.version import VersionView
|
from authentik.admin.api.version import VersionView
|
||||||
from authentik.admin.api.workers import WorkerView
|
from authentik.admin.api.workers import WorkerView
|
||||||
from authentik.api.v3.config import ConfigView
|
from authentik.api.v3.config import ConfigView
|
||||||
from authentik.api.v3.sentry import SentryTunnelView
|
|
||||||
from authentik.api.views import APIBrowserView
|
from authentik.api.views import APIBrowserView
|
||||||
from authentik.core.api.applications import ApplicationViewSet
|
from authentik.core.api.applications import ApplicationViewSet
|
||||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
||||||
|
@ -249,7 +248,6 @@ urlpatterns = (
|
||||||
FlowInspectorView.as_view(),
|
FlowInspectorView.as_view(),
|
||||||
name="flow-inspector",
|
name="flow-inspector",
|
||||||
),
|
),
|
||||||
path("sentry/", SentryTunnelView.as_view(), name="sentry"),
|
|
||||||
path("schema/", cache_page(86400)(SpectacularAPIView.as_view()), name="schema"),
|
path("schema/", cache_page(86400)(SpectacularAPIView.as_view()), name="schema"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,7 +38,7 @@ func main() {
|
||||||
|
|
||||||
if config.G.ErrorReporting.Enabled {
|
if config.G.ErrorReporting.Enabled {
|
||||||
err := sentry.Init(sentry.ClientOptions{
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
Dsn: config.G.ErrorReporting.DSN,
|
||||||
AttachStacktrace: true,
|
AttachStacktrace: true,
|
||||||
TracesSampleRate: 0.6,
|
TracesSampleRate: 0.6,
|
||||||
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
|
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
|
||||||
|
|
|
@ -26,6 +26,7 @@ func DefaultConfig() {
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
ErrorReporting: ErrorReportingConfig{
|
ErrorReporting: ErrorReportingConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
DSN: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,4 +42,5 @@ type ErrorReportingConfig struct {
|
||||||
Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"`
|
Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"`
|
||||||
Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"`
|
Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"`
|
||||||
SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"`
|
SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"`
|
||||||
|
DSN string
|
||||||
}
|
}
|
||||||
|
|
53
internal/web/sentry_proxy.go
Normal file
53
internal/web/sentry_proxy.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"goauthentik.io/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SentryRequest struct {
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) APISentryProxy(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
if !config.G.ErrorReporting.Enabled {
|
||||||
|
ws.log.Debug("error reporting disabled")
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fullBody, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
ws.log.Debug("failed to read body")
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(fullBody), "\n")
|
||||||
|
if len(lines) < 1 {
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sd := SentryRequest{}
|
||||||
|
ws.log.Debug(lines[0])
|
||||||
|
err = json.Unmarshal([]byte(lines[0]), &sd)
|
||||||
|
if err != nil {
|
||||||
|
ws.log.WithError(err).Warning("failed to parse sentry request")
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sd.DSN != config.G.ErrorReporting.DSN {
|
||||||
|
ws.log.WithField("have", sd.DSN).WithField("expected", config.G.ErrorReporting.DSN).Debug("invalid DSN")
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Post("https://sentry.beryju.org/api/8/envelope/", "application/octet-stream", strings.NewReader(string(fullBody)))
|
||||||
|
if err != nil {
|
||||||
|
ws.log.WithError(err).Warning("failed to proxy sentry")
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.WriteHeader(res.StatusCode)
|
||||||
|
}
|
|
@ -51,10 +51,15 @@ func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
|
||||||
p: g,
|
p: g,
|
||||||
}
|
}
|
||||||
ws.configureStatic()
|
ws.configureStatic()
|
||||||
|
ws.configureRoutes()
|
||||||
ws.configureProxy()
|
ws.configureProxy()
|
||||||
return ws
|
return ws
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) configureRoutes() {
|
||||||
|
ws.m.Path("/api/v3/sentry/").HandlerFunc(ws.APISentryProxy)
|
||||||
|
}
|
||||||
|
|
||||||
func (ws *WebServer) Start() {
|
func (ws *WebServer) Start() {
|
||||||
go ws.listenPlain()
|
go ws.listenPlain()
|
||||||
go ws.listenTLS()
|
go ws.listenTLS()
|
||||||
|
|
Reference in a new issue