diff --git a/README.md b/README.md index f4138083e..fe7778212 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,23 @@ See [Development Documentation](https://goauthentik.io/developer-docs/?utm_sourc ## Security See [SECURITY.md](SECURITY.md) + +## Sponsors + +This project is proudly sponsored by: + +

+ + + +

+ +DigitalOcean provides development and testing resources for authentik. + +

+ + Deploys by Netlify + +

+ +Netlify hosts the [goauthentik.io](goauthentik.io) site. diff --git a/authentik/core/models.py b/authentik/core/models.py index 136db1c14..33508aac8 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -278,7 +278,13 @@ class Application(PolicyBindingModel): """Get casted provider instance""" if not self.provider: return None - return Provider.objects.get_subclass(pk=self.provider.pk) + # if the Application class has been cache, self.provider is set + # but doing a direct query lookup will fail. + # In that case, just return None + try: + return Provider.objects.get_subclass(pk=self.provider.pk) + except Provider.DoesNotExist: + return None def __str__(self): return self.name diff --git a/authentik/events/models.py b/authentik/events/models.py index d08ab1e99..c10403488 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -2,7 +2,7 @@ import time from collections import Counter from datetime import timedelta -from inspect import getmodule, stack +from inspect import currentframe from smtplib import SMTPException from typing import TYPE_CHECKING, Optional, Type, Union from uuid import uuid4 @@ -192,14 +192,15 @@ class Event(ExpiringModel): def new( action: Union[str, EventAction], app: Optional[str] = None, - _inspect_offset: int = 1, **kwargs, ) -> "Event": """Create new Event instance from arguments. Instance is NOT saved.""" if not isinstance(action, EventAction): action = EventAction.CUSTOM_PREFIX + action if not app: - app = getmodule(stack()[_inspect_offset][0]).__name__ + current = currentframe() + parent = current.f_back + app = parent.f_globals["__name__"] cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs)) event = Event(action=action, app=app, context=cleaned_kwargs) return event diff --git a/authentik/root/settings.py b/authentik/root/settings.py index e1bd798ea..e22134f7c 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -28,6 +28,7 @@ from sentry_sdk.integrations.boto3 import Boto3Integration from sentry_sdk.integrations.celery import CeleryIntegration from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.redis import RedisIntegration +from sentry_sdk.integrations.threading import ThreadingIntegration from authentik import ENV_GIT_HASH_KEY, __version__ from authentik.core.middleware import structlog_add_request_id @@ -424,6 +425,7 @@ if _ERROR_REPORTING: CeleryIntegration(), RedisIntegration(), Boto3Integration(), + ThreadingIntegration(propagate_hub=True), ], before_send=before_send, release=f"authentik@{__version__}", diff --git a/internal/outpost/flow.go b/internal/outpost/flow.go index 830a6ed62..f19d22b0a 100644 --- a/internal/outpost/flow.go +++ b/internal/outpost/flow.go @@ -60,6 +60,7 @@ type FlowExecutor struct { func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor { rsp := sentry.StartSpan(ctx, "authentik.outposts.flow_executor") + rsp.Description = flowSlug l := log.WithField("flow", flowSlug).WithFields(logFields) jar, err := cookiejar.New(nil) @@ -153,8 +154,8 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) { } ch := challenge.GetActualInstance().(ChallengeInt) fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge") - gcsp.SetTag("ak_challenge", string(ch.GetType())) - gcsp.SetTag("ak_component", ch.GetComponent()) + gcsp.SetTag("authentik.flow.challenge", string(ch.GetType())) + gcsp.SetTag("authentik.flow.component", ch.GetComponent()) gcsp.Finish() FlowTimingGet.With(prometheus.Labels{ "stage": ch.GetComponent(), @@ -202,8 +203,8 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) { response, _, err := responseReq.Execute() ch = response.GetActualInstance().(ChallengeInt) fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response") - scsp.SetTag("ak_challenge", string(ch.GetType())) - scsp.SetTag("ak_component", ch.GetComponent()) + scsp.SetTag("authentik.flow.challenge", string(ch.GetType())) + scsp.SetTag("authentik.flow.component", ch.GetComponent()) scsp.Finish() switch ch.GetComponent() { diff --git a/internal/outpost/ldap/bind/request.go b/internal/outpost/ldap/bind/request.go index 43d379282..59827105d 100644 --- a/internal/outpost/ldap/bind/request.go +++ b/internal/outpost/ldap/bind/request.go @@ -23,9 +23,18 @@ type Request struct { func NewRequest(bindDN string, bindPW string, conn net.Conn) (*Request, *sentry.Span) { span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.bind", sentry.TransactionName("authentik.providers.ldap.bind")) + span.Description = bindDN rid := uuid.New().String() span.SetTag("request_uid", rid) - span.SetTag("user.username", bindDN) + hub := sentry.GetHubFromContext(span.Context()) + if hub == nil { + hub = sentry.CurrentHub() + } + hub.Scope().SetUser(sentry.User{ + Username: bindDN, + ID: bindDN, + IPAddress: utils.GetIP(conn.RemoteAddr()), + }) bindDN = strings.ToLower(bindDN) return &Request{ diff --git a/internal/outpost/ldap/search/request.go b/internal/outpost/ldap/search/request.go index 183dba226..5f7e9af90 100644 --- a/internal/outpost/ldap/search/request.go +++ b/internal/outpost/ldap/search/request.go @@ -2,6 +2,7 @@ package search import ( "context" + "fmt" "net" "strings" @@ -27,10 +28,19 @@ func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Re bindDN = strings.ToLower(bindDN) searchReq.BaseDN = strings.ToLower(searchReq.BaseDN) span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search")) + span.Description = fmt.Sprintf("%s (%s)", searchReq.BaseDN, ldap.ScopeMap[searchReq.Scope]) span.SetTag("request_uid", rid) - span.SetTag("user.username", bindDN) - span.SetTag("ak_filter", searchReq.Filter) - span.SetTag("ak_base_dn", searchReq.BaseDN) + hub := sentry.GetHubFromContext(span.Context()) + if hub == nil { + hub = sentry.CurrentHub() + } + hub.Scope().SetUser(sentry.User{ + Username: bindDN, + ID: bindDN, + IPAddress: utils.GetIP(conn.RemoteAddr()), + }) + span.SetTag("ldap_filter", searchReq.Filter) + span.SetTag("ldap_base_dn", searchReq.BaseDN) return &Request{ SearchRequest: searchReq, BindDN: bindDN, diff --git a/internal/outpost/proxyv2/application/application.go b/internal/outpost/proxyv2/application/application.go index b500a5773..f6d48fcc8 100644 --- a/internal/outpost/proxyv2/application/application.go +++ b/internal/outpost/proxyv2/application/application.go @@ -12,6 +12,8 @@ import ( "time" "github.com/coreos/go-oidc" + "github.com/getsentry/sentry-go" + sentryhttp "github.com/getsentry/sentry-go/http" "github.com/gorilla/mux" "github.com/gorilla/sessions" "github.com/pkg/errors" @@ -109,6 +111,15 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore user := "" if c != nil { user = c.PreferredUsername + hub := sentry.GetHubFromContext(r.Context()) + if hub == nil { + hub = sentry.CurrentHub() + } + hub.Scope().SetUser(sentry.User{ + Username: user, + ID: c.Sub, + IPAddress: r.RemoteAddr, + }) } before := time.Now() inner.ServeHTTP(rw, r) @@ -124,6 +135,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore }).Observe(float64(after)) }) }) + mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) // Support /start and /sign_in for backwards compatibility mux.HandleFunc("/akprox/start", a.handleRedirect) diff --git a/internal/outpost/proxyv2/proxyv2.go b/internal/outpost/proxyv2/proxyv2.go index f6cd68ff1..dbf481801 100644 --- a/internal/outpost/proxyv2/proxyv2.go +++ b/internal/outpost/proxyv2/proxyv2.go @@ -10,6 +10,7 @@ import ( "sync" "time" + sentryhttp "github.com/getsentry/sentry-go/http" "github.com/gorilla/mux" "github.com/pires/go-proxyproto" log "github.com/sirupsen/logrus" @@ -52,6 +53,7 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer { globalMux := rootMux.NewRoute().Subrouter() globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil)) + globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle) s := &ProxyServer{ Listen: "0.0.0.0:%d", PortOffset: portOffset, diff --git a/internal/utils/web/middleware.go b/internal/utils/web/middleware.go index 0c5f94f82..2b66e0d05 100644 --- a/internal/utils/web/middleware.go +++ b/internal/utils/web/middleware.go @@ -99,8 +99,8 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.handler.ServeHTTP(responseLogger, req) duration := float64(time.Since(t)) / float64(time.Millisecond) h.afterHandler(h.logger.WithFields(log.Fields{ - "host": req.RemoteAddr, - "vhost": GetHost(req), + "remote": req.RemoteAddr, + "host": GetHost(req), "request_protocol": req.Proto, "runtime": fmt.Sprintf("%0.3f", duration), "method": req.Method, diff --git a/internal/web/middleware_log.go b/internal/web/middleware_log.go deleted file mode 100644 index 246bbf452..000000000 --- a/internal/web/middleware_log.go +++ /dev/null @@ -1,29 +0,0 @@ -package web - -import ( - "net/http" - "time" - - "github.com/getsentry/sentry-go" - log "github.com/sirupsen/logrus" - "goauthentik.io/internal/utils/web" -) - -func loggingMiddleware(l *log.Entry) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - span := sentry.StartSpan(r.Context(), "authentik.go.request") - before := time.Now() - // Call the next handler, which can be another middleware in the chain, or the final handler. - next.ServeHTTP(w, r) - after := time.Now() - l.WithFields(log.Fields{ - "remote": r.RemoteAddr, - "method": r.Method, - "took": after.Sub(before), - "host": web.GetHost(r), - }).Info(r.RequestURI) - span.Finish() - }) - } -} diff --git a/internal/web/middleware_sentry.go b/internal/web/middleware_sentry.go deleted file mode 100644 index 88329e66c..000000000 --- a/internal/web/middleware_sentry.go +++ /dev/null @@ -1,44 +0,0 @@ -package web - -import ( - "encoding/json" - "net/http" - - sentryhttp "github.com/getsentry/sentry-go/http" - log "github.com/sirupsen/logrus" -) - -func recoveryMiddleware() func(next http.Handler) http.Handler { - sentryHandler := sentryhttp.New(sentryhttp.Options{}) - l := log.WithField("logger", "authentik.router.sentry") - return func(next http.Handler) http.Handler { - sentryHandler.Handle(next) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - next.ServeHTTP(w, r) - defer func() { - re := recover() - if re == nil { - return - } - err := re.(error) - if err != nil { - l.WithError(err).Warning("global panic handler") - jsonBody, _ := json.Marshal(struct { - Successful bool - Error string - }{ - Successful: false, - Error: err.Error(), - }) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - _, err := w.Write(jsonBody) - if err != nil { - l.WithError(err).Warning("Failed to write sentry error body") - } - } - }() - }) - } -} diff --git a/internal/web/web.go b/internal/web/web.go index 4096f91dc..5ddd21f72 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -6,6 +6,7 @@ import ( "net" "net/http" + sentryhttp "github.com/getsentry/sentry-go/http" "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/pires/go-proxyproto" @@ -13,6 +14,7 @@ import ( "goauthentik.io/internal/config" "goauthentik.io/internal/gounicorn" "goauthentik.io/internal/outpost/proxyv2" + "goauthentik.io/internal/utils/web" ) type WebServer struct { @@ -34,13 +36,11 @@ type WebServer struct { func NewWebServer(g *gounicorn.GoUnicorn) *WebServer { l := log.WithField("logger", "authentik.router") mainHandler := mux.NewRouter() - if config.G.ErrorReporting.Enabled { - mainHandler.Use(recoveryMiddleware()) - } + mainHandler.Use(sentryhttp.New(sentryhttp.Options{}).Handle) mainHandler.Use(handlers.ProxyHeaders) mainHandler.Use(handlers.CompressHandler) logginRouter := mainHandler.NewRoute().Subrouter() - logginRouter.Use(loggingMiddleware(l)) + logginRouter.Use(web.NewLoggingHandler(l, nil)) ws := &WebServer{ LegacyProxy: true, @@ -72,7 +72,7 @@ func (ws *WebServer) Shutdown() { func (ws *WebServer) listenPlain() { ln, err := net.Listen("tcp", config.G.Web.Listen) if err != nil { - ws.log.WithError(err).Fatalf("failed to listen") + ws.log.WithError(err).Fatal("failed to listen") } ws.log.WithField("listen", config.G.Web.Listen).Info("Listening") @@ -83,7 +83,7 @@ func (ws *WebServer) listenPlain() { err = http.ListenAndServe(config.G.Web.Listen, ws.m) if err != nil && !errors.Is(err, http.ErrServerClosed) { - ws.log.Errorf("ERROR: http.Serve() - %s", err) + ws.log.WithError(err).Error("failed to listen") } } @@ -100,14 +100,14 @@ func (ws *WebServer) serve(listener net.Listener) { // We received an interrupt signal, shut down. if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: - ws.log.Printf("HTTP server Shutdown: %v", err) + ws.log.WithError(err).Warning("HTTP server Shutdown") } close(idleConnsClosed) }() err := srv.Serve(listener) if err != nil && !errors.Is(err, http.ErrServerClosed) { - ws.log.Errorf("ERROR: http.Serve() - %s", err) + ws.log.WithError(err).Error("ERROR: http.Serve()") } <-idleConnsClosed } diff --git a/lifecycle/ak b/lifecycle/ak index 03759d0e5..b857c588e 100755 --- a/lifecycle/ak +++ b/lifecycle/ak @@ -67,7 +67,7 @@ if [[ "$1" == "server" ]]; then /authentik-proxy elif [[ "$1" == "worker" ]]; then echo "worker" > $MODE_FILE - check_if_root "celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events" + check_if_root "celery -A authentik.root.celery worker -Ofair --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events" elif [[ "$1" == "flower" ]]; then echo "flower" > $MODE_FILE celery -A authentik.root.celery flower diff --git a/web/src/elements/Markdown.ts b/web/src/elements/Markdown.ts new file mode 100644 index 000000000..d0ff3279a --- /dev/null +++ b/web/src/elements/Markdown.ts @@ -0,0 +1,32 @@ +import { CSSResult, LitElement, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; + +import AKGlobal from "../authentik.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; + +export interface MarkdownDocument { + html: string; + metadata: { [key: string]: string }; + filename: string; +} + +@customElement("ak-markdown") +export class Markdown extends LitElement { + @property({ attribute: false }) + md?: MarkdownDocument; + + static get styles(): CSSResult[] { + return [PFList, PFContent, AKGlobal]; + } + + render(): TemplateResult { + if (!this.md) { + return html``; + } + const finalHTML = this.md?.html.replace("