From 276af8457d4552864b909c39cff85154fe9209a9 Mon Sep 17 00:00:00 2001 From: Jens L Date: Tue, 15 Nov 2022 16:05:29 +0100 Subject: [PATCH] root: make sentry DSN configurable (#4016) * make sentry DSN configurable Signed-off-by: Jens Langhammer * make proxy smarter Signed-off-by: Jens Langhammer * fix typo in config struct Signed-off-by: Jens Langhammer Signed-off-by: Jens Langhammer --- authentik/api/v3/config.py | 2 + authentik/lib/default.yml | 1 + authentik/lib/sentry.py | 3 +- cmd/server/main.go | 2 +- internal/config/config.go | 5 +- internal/config/struct.go | 8 +-- internal/outpost/ak/global.go | 3 +- internal/outpost/ak/test.go | 2 +- internal/web/sentry_proxy.go | 82 ++++++++++++++-------- internal/web/web.go | 2 +- schema.yml | 4 ++ web/src/common/sentry.ts | 2 +- website/docs/installation/configuration.md | 13 +++- 13 files changed, 84 insertions(+), 45 deletions(-) diff --git a/authentik/api/v3/config.py b/authentik/api/v3/config.py index 93daa6dd2..952a6b637 100644 --- a/authentik/api/v3/config.py +++ b/authentik/api/v3/config.py @@ -35,6 +35,7 @@ class ErrorReportingConfigSerializer(PassiveSerializer): """Config for error reporting""" enabled = BooleanField(read_only=True) + sentry_dsn = CharField(read_only=True) environment = CharField(read_only=True) send_pii = BooleanField(read_only=True) traces_sample_rate = FloatField(read_only=True) @@ -77,6 +78,7 @@ class ConfigView(APIView): { "error_reporting": { "enabled": CONFIG.y("error_reporting.enabled"), + "sentry_dsn": CONFIG.y("error_reporting.sentry_dsn"), "environment": CONFIG.y("error_reporting.environment"), "send_pii": CONFIG.y("error_reporting.send_pii"), "traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)), diff --git a/authentik/lib/default.yml b/authentik/lib/default.yml index e4668bcc3..b6ba9c266 100644 --- a/authentik/lib/default.yml +++ b/authentik/lib/default.yml @@ -32,6 +32,7 @@ log_level: info # Error reporting, sends stacktrace to sentry.beryju.org error_reporting: enabled: false + sentry_dsn: https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8 environment: customer send_pii: false sample_rate: 0.1 diff --git a/authentik/lib/sentry.py b/authentik/lib/sentry.py index fe3cc2bed..f582705fe 100644 --- a/authentik/lib/sentry.py +++ b/authentik/lib/sentry.py @@ -34,7 +34,6 @@ from authentik.lib.utils.http import authentik_user_agent from authentik.lib.utils.reflection import class_to_path, get_env LOGGER = get_logger() -SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8" class SentryWSMiddleware(BaseMiddleware): @@ -71,7 +70,7 @@ def sentry_init(**sentry_init_kwargs): kwargs.update(**sentry_init_kwargs) # pylint: disable=abstract-class-instantiated sentry_sdk_init( - dsn=SENTRY_DSN, + dsn=CONFIG.y("error_reporting.sentry_dsn"), integrations=[ DjangoIntegration(transaction_style="function_name"), CeleryIntegration(), diff --git a/cmd/server/main.go b/cmd/server/main.go index d650f87ea..1d8af6d6e 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -38,7 +38,7 @@ func main() { if config.Get().ErrorReporting.Enabled { err := sentry.Init(sentry.ClientOptions{ - Dsn: config.Get().ErrorReporting.DSN, + Dsn: config.Get().ErrorReporting.SentryDSN, AttachStacktrace: true, TracesSampler: sentryutils.SamplerFunc(config.Get().ErrorReporting.SampleRate), Release: fmt.Sprintf("authentik@%s", constants.VERSION), diff --git a/internal/config/config.go b/internal/config/config.go index c97be3633..07a95da52 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,7 +40,6 @@ func defaultConfig() *Config { LogLevel: "info", ErrorReporting: ErrorReportingConfig{ Enabled: false, - DSN: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", SampleRate: 1, }, } @@ -63,11 +62,11 @@ func (c *Config) Setup(paths ...string) { func (c *Config) LoadConfig(path string) error { raw, err := os.ReadFile(path) if err != nil { - return fmt.Errorf("Failed to load config file: %w", err) + return fmt.Errorf("failed to load config file: %w", err) } err = yaml.Unmarshal(raw, c) if err != nil { - return fmt.Errorf("Failed to parse YAML: %w", err) + return fmt.Errorf("failed to parse YAML: %w", err) } c.walkScheme(c) log.WithField("path", path).Debug("Loaded config") diff --git a/internal/config/struct.go b/internal/config/struct.go index 82d5437be..9a087376a 100644 --- a/internal/config/struct.go +++ b/internal/config/struct.go @@ -38,10 +38,10 @@ type PathsConfig struct { } type ErrorReportingConfig struct { - Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"` - Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"` - SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"` - DSN string + Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"` + SentryDSN string `yaml:"sentry_dsn" env:"AUTHENTIK_ERROR_REPORTING__SENTRY_DSN"` + Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"` + SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"` SampleRate float64 `yaml:"sample_rate" env:"AUTHENTIK_ERROR_REPORTING__SAMPLE_RATE"` } diff --git a/internal/outpost/ak/global.go b/internal/outpost/ak/global.go index c8142f69b..f70d4a5e3 100644 --- a/internal/outpost/ak/global.go +++ b/internal/outpost/ak/global.go @@ -44,12 +44,11 @@ func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) { } if globalConfig.ErrorReporting.Enabled { - dsn := "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8" if !initialSetup { l.WithField("env", globalConfig.ErrorReporting.Environment).Debug("Error reporting enabled") } err := sentry.Init(sentry.ClientOptions{ - Dsn: dsn, + Dsn: globalConfig.ErrorReporting.SentryDsn, Environment: globalConfig.ErrorReporting.Environment, TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)), Release: fmt.Sprintf("authentik@%s", constants.VERSION), diff --git a/internal/outpost/ak/test.go b/internal/outpost/ak/test.go index 7b9f4e832..82a92351e 100644 --- a/internal/outpost/ak/test.go +++ b/internal/outpost/ak/test.go @@ -19,7 +19,7 @@ func TestSecret() string { func MockConfig() api.Config { return *api.NewConfig( - *api.NewErrorReportingConfig(false, "test", false, 0.0), + *api.NewErrorReportingConfig(false, "https://foo.bar/9", "test", false, 0.0), []api.CapabilitiesEnum{}, 100, 100, diff --git a/internal/web/sentry_proxy.go b/internal/web/sentry_proxy.go index f6f0c6e51..378eab9b9 100644 --- a/internal/web/sentry_proxy.go +++ b/internal/web/sentry_proxy.go @@ -3,8 +3,11 @@ package web import ( "bytes" "encoding/json" + "fmt" "io" "net/http" + "net/url" + "strconv" "strings" "goauthentik.io/internal/config" @@ -14,41 +17,62 @@ type SentryRequest struct { DSN string `json:"dsn"` } -func (ws *WebServer) APISentryProxy(rw http.ResponseWriter, r *http.Request) { +func (ws *WebServer) APISentryProxy() http.HandlerFunc { + fallbackHandler := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + } if !config.Get().ErrorReporting.Enabled { ws.log.Debug("error reporting disabled") - rw.WriteHeader(http.StatusBadRequest) - return + return fallbackHandler } - fb := &bytes.Buffer{} - _, err := io.Copy(fb, r.Body) + dsn, err := url.Parse(config.Get().ErrorReporting.SentryDSN) if err != nil { - ws.log.Debug("failed to read body") - rw.WriteHeader(http.StatusBadRequest) - return + ws.log.WithError(err).Warning("invalid sentry DSN") + return fallbackHandler } - lines := strings.Split(fb.String(), "\n") - if len(lines) < 1 { - rw.WriteHeader(http.StatusBadRequest) - return - } - sd := SentryRequest{} - err = json.Unmarshal([]byte(lines[0]), &sd) + projectId, err := strconv.Atoi(strings.TrimPrefix(dsn.Path, "/")) if err != nil { - ws.log.WithError(err).Warning("failed to parse sentry request") - rw.WriteHeader(http.StatusBadRequest) - return + ws.log.WithError(err).Warning("failed to get sentry project id") + return fallbackHandler } - if sd.DSN != config.Get().ErrorReporting.DSN { - ws.log.WithField("have", sd.DSN).WithField("expected", config.Get().ErrorReporting.DSN).Debug("invalid DSN") - rw.WriteHeader(http.StatusBadRequest) - return + return func(rw http.ResponseWriter, r *http.Request) { + fb := &bytes.Buffer{} + _, err := io.Copy(fb, r.Body) + if err != nil { + ws.log.Debug("failed to read body") + rw.WriteHeader(http.StatusBadRequest) + return + } + lines := strings.Split(fb.String(), "\n") + if len(lines) < 1 { + rw.WriteHeader(http.StatusBadRequest) + return + } + sd := SentryRequest{} + 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.Get().ErrorReporting.SentryDSN { + rw.WriteHeader(http.StatusBadRequest) + return + } + res, err := http.DefaultClient.Post( + fmt.Sprintf( + "https://%s/api/%d/envelope/", + dsn.Host, + projectId, + ), + "application/x-sentry-envelope", + fb, + ) + if err != nil { + ws.log.WithError(err).Warning("failed to proxy sentry") + rw.WriteHeader(http.StatusBadRequest) + return + } + rw.WriteHeader(res.StatusCode) } - res, err := http.DefaultClient.Post("https://sentry.beryju.org/api/8/envelope/", "application/octet-stream", fb) - if err != nil { - ws.log.WithError(err).Warning("failed to proxy sentry") - rw.WriteHeader(http.StatusBadRequest) - return - } - rw.WriteHeader(res.StatusCode) } diff --git a/internal/web/web.go b/internal/web/web.go index c73931ca6..13422ea54 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -53,7 +53,7 @@ func NewWebServer(g *gounicorn.GoUnicorn) *WebServer { } func (ws *WebServer) configureRoutes() { - ws.m.Path("/api/v3/sentry/").HandlerFunc(ws.APISentryProxy) + ws.m.Path("/api/v3/sentry/").HandlerFunc(ws.APISentryProxy()) } func (ws *WebServer) Start() { diff --git a/schema.yml b/schema.yml index 104ba041c..c8e3886e9 100644 --- a/schema.yml +++ b/schema.yml @@ -27162,6 +27162,9 @@ components: enabled: type: boolean readOnly: true + sentry_dsn: + type: string + readOnly: true environment: type: string readOnly: true @@ -27176,6 +27179,7 @@ components: - enabled - environment - send_pii + - sentry_dsn - traces_sample_rate Event: type: object diff --git a/web/src/common/sentry.ts b/web/src/common/sentry.ts index 0884fdde6..31c7c705d 100644 --- a/web/src/common/sentry.ts +++ b/web/src/common/sentry.ts @@ -14,7 +14,7 @@ export async function configureSentry(canDoPpi = false): Promise { const cfg = await config(); if (cfg.errorReporting.enabled) { Sentry.init({ - dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", + dsn: cfg.errorReporting.sentryDsn, ignoreErrors: [ /network/gi, /fetch/gi, diff --git a/website/docs/installation/configuration.md b/website/docs/installation/configuration.md index a38679a5f..586fbdca5 100644 --- a/website/docs/installation/configuration.md +++ b/website/docs/installation/configuration.md @@ -90,9 +90,20 @@ Disable the inbuilt update-checker. Defaults to `false`. Error reports are sent to https://sentry.beryju.org, and are used for debugging and general feedback. Anonymous performance data is also sent. +- `AUTHENTIK_ERROR_REPORTING__SENTRY_DSN` + + Sets the DSN for the Sentry API endpoint. Defaults to `https://sentry.beryju.org`. + + When error reporting is enabled, the default Sentry DSN will allow the authentik developers to receive error reports and anonymous performance data, which is used for general feedback about authentik, and in some cases, may be used for debugging purposes. + + Users can create their own hosted Sentry account (or self-host Sentry) and opt to collect this data themselves. + - `AUTHENTIK_ERROR_REPORTING__ENVIRONMENT` - Unique environment that is attached to your error reports, should be set to your email address for example. Defaults to `customer`. + The environment tag associated with all data sent to Sentry. Defaults to `customer`. + + When error reporting has been enabled to aid in debugging issues, this should be set to a unique + value, such as an e-mail address. - `AUTHENTIK_ERROR_REPORTING__SEND_PII`