events: catch unhandled exceptions from request as event, add button to open github issue

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-06-14 17:22:58 +02:00
parent 0e02925a3d
commit e584fd1344
8 changed files with 93 additions and 10 deletions

View file

@ -12,6 +12,7 @@ from authentik.core.models import User
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread from authentik.events.signals import EventNewThread
from authentik.events.utils import model_to_dict from authentik.events.utils import model_to_dict
from authentik.lib.utils.errors import exception_to_string
class AuditMiddleware: class AuditMiddleware:
@ -54,7 +55,14 @@ class AuditMiddleware:
# pylint: disable=unused-argument # pylint: disable=unused-argument
def process_exception(self, request: HttpRequest, exception: Exception): def process_exception(self, request: HttpRequest, exception: Exception):
"""Unregister handlers in case of exception""" """Disconnect handlers in case of exception"""
thread = EventNewThread(
EventAction.SYSTEM_EXCEPTION,
request,
message=exception_to_string(exception),
)
thread.run()
post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"])
pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"])

View file

@ -71,6 +71,7 @@ class EventAction(models.TextChoices):
SYSTEM_TASK_EXECUTION = "system_task_execution" SYSTEM_TASK_EXECUTION = "system_task_execution"
SYSTEM_TASK_EXCEPTION = "system_task_exception" SYSTEM_TASK_EXCEPTION = "system_task_exception"
SYSTEM_EXCEPTION = "system_exception"
CONFIGURATION_ERROR = "configuration_error" CONFIGURATION_ERROR = "configuration_error"

View file

@ -0,0 +1,10 @@
"""error utils"""
from traceback import format_tb
TRACEBACK_HEADER = "Traceback (most recent call last):\n"
def exception_to_string(exc: Exception) -> str:
"""Convert exception to string stackrace"""
# Either use passed original exception or whatever we have
return TRACEBACK_HEADER + "".join(format_tb(exc.__traceback__)) + str(exc)

View file

@ -1,7 +1,6 @@
"""authentik policy task""" """authentik policy task"""
from multiprocessing import get_context from multiprocessing import get_context
from multiprocessing.connection import Connection from multiprocessing.connection import Connection
from traceback import format_tb
from typing import Optional from typing import Optional
from django.core.cache import cache from django.core.cache import cache
@ -11,12 +10,12 @@ from sentry_sdk.tracing import Span
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.exceptions import PolicyException from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.policies.types import PolicyRequest, PolicyResult from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger() LOGGER = get_logger()
TRACEBACK_HEADER = "Traceback (most recent call last):\n"
FORK_CTX = get_context("fork") FORK_CTX = get_context("fork")
PROCESS_CLASS = FORK_CTX.Process PROCESS_CLASS = FORK_CTX.Process
@ -106,11 +105,7 @@ class PolicyProcess(PROCESS_CLASS):
except PolicyException as exc: except PolicyException as exc:
# Either use passed original exception or whatever we have # Either use passed original exception or whatever we have
src_exc = exc.src_exc if exc.src_exc else exc src_exc = exc.src_exc if exc.src_exc else exc
error_string = ( error_string = exception_to_string(src_exc)
TRACEBACK_HEADER
+ "".join(format_tb(src_exc.__traceback__))
+ str(src_exc)
)
# Create policy exception event, only when we're not debugging # Create policy exception event, only when we're not debugging
if not self.request.debug: if not self.request.debug:
self.create_event(EventAction.POLICY_EXCEPTION, message=error_string) self.create_event(EventAction.POLICY_EXCEPTION, message=error_string)

View file

@ -19153,6 +19153,7 @@ components:
- property_mapping_exception - property_mapping_exception
- system_task_execution - system_task_execution
- system_task_exception - system_task_exception
- system_exception
- configuration_error - configuration_error
- model_created - model_created
- model_updated - model_updated

View file

@ -1391,6 +1391,7 @@ msgstr "Event {0}"
msgid "Events" msgid "Events"
msgstr "Events" msgstr "Events"
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
msgid "Exception" msgid "Exception"
@ -2497,6 +2498,10 @@ msgstr "Only send notification once, for example when sending a webhook into a c
msgid "Open application" msgid "Open application"
msgstr "Open application" msgstr "Open application"
#: src/pages/events/EventInfo.ts
msgid "Open issue on GitHub..."
msgstr "Open issue on GitHub..."
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts #: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
msgid "OpenID Configuration Issuer" msgid "OpenID Configuration Issuer"
msgstr "OpenID Configuration Issuer" msgstr "OpenID Configuration Issuer"

View file

@ -1383,6 +1383,7 @@ msgstr ""
msgid "Events" msgid "Events"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Exception" msgid "Exception"
@ -2489,6 +2490,10 @@ msgstr ""
msgid "Open application" msgid "Open application"
msgstr "" msgstr ""
#:
msgid "Open issue on GitHub..."
msgstr ""
#: #:
msgid "OpenID Configuration Issuer" msgid "OpenID Configuration Issuer"
msgstr "" msgstr ""

View file

@ -7,11 +7,12 @@ import "../../elements/Expand";
import { PFSize } from "../../elements/Spinner"; import { PFSize } from "../../elements/Spinner";
import { EventContext, EventWithContext } from "../../api/Events"; import { EventContext, EventWithContext } from "../../api/Events";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFList from "@patternfly/patternfly/components/List/list.css"; import PFList from "@patternfly/patternfly/components/List/list.css";
import { VERSION } from "../../constants";
@customElement("ak-event-info") @customElement("ak-event-info")
export class EventInfo extends LitElement { export class EventInfo extends LitElement {
@ -20,7 +21,7 @@ export class EventInfo extends LitElement {
event!: EventWithContext; event!: EventWithContext;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFFlex, PFList, PFDescriptionList, return [PFBase, PFButton, PFFlex, PFList, PFDescriptionList,
css` css`
code { code {
display: block; display: block;
@ -137,6 +138,45 @@ export class EventInfo extends LitElement {
</div>`; </div>`;
} }
buildGitHubIssueUrl(title: string, body: string): string {
// https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/about-automation-for-issues-and-pull-requests-with-query-parameters
const fullBody = `
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
<details>
<summary>Stacktrace from authentik</summary>
\`\`\`
${body}
\`\`\`
</details>
**Version and Deployment (please complete the following information):**
- authentik version: ${VERSION}
- Deployment: [e.g. docker-compose, helm]
**Additional context**
Add any other context about the problem here.
`;
return `https://github.com/goauthentik/authentik/issues/new?labels=bug+from_authentik&title=${encodeURIComponent(title)}&body=${encodeURIComponent(fullBody)}`;
}
render(): TemplateResult { render(): TemplateResult {
if (!this.event) { if (!this.event) {
return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`;
@ -176,6 +216,24 @@ export class EventInfo extends LitElement {
return html` return html`
<h3>${t`Secret:`}</h3> <h3>${t`Secret:`}</h3>
${this.getModelInfo(this.event.context.secret as EventContext)}`; ${this.getModelInfo(this.event.context.secret as EventContext)}`;
case EventMatcherPolicyActionEnum.SystemException:
return html`
<a
class="pf-c-button pf-m-primary"
target="_blank"
href=${this.buildGitHubIssueUrl(
"",
this.event.context.message as string
)}>
${t`Open issue on GitHub...`}
</a>
<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventMatcherPolicyActionEnum.PropertyMappingException: case EventMatcherPolicyActionEnum.PropertyMappingException:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">