root: make sentry DSN configurable (#4016)

* make sentry DSN configurable

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* make proxy smarter

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix typo in config struct

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2022-11-15 16:05:29 +01:00 committed by GitHub
parent a9111bd3fd
commit 276af8457d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 84 additions and 45 deletions

View File

@ -35,6 +35,7 @@ class ErrorReportingConfigSerializer(PassiveSerializer):
"""Config for error reporting""" """Config for error reporting"""
enabled = BooleanField(read_only=True) enabled = BooleanField(read_only=True)
sentry_dsn = CharField(read_only=True)
environment = CharField(read_only=True) environment = CharField(read_only=True)
send_pii = BooleanField(read_only=True) send_pii = BooleanField(read_only=True)
traces_sample_rate = FloatField(read_only=True) traces_sample_rate = FloatField(read_only=True)
@ -77,6 +78,7 @@ class ConfigView(APIView):
{ {
"error_reporting": { "error_reporting": {
"enabled": CONFIG.y("error_reporting.enabled"), "enabled": CONFIG.y("error_reporting.enabled"),
"sentry_dsn": CONFIG.y("error_reporting.sentry_dsn"),
"environment": CONFIG.y("error_reporting.environment"), "environment": CONFIG.y("error_reporting.environment"),
"send_pii": CONFIG.y("error_reporting.send_pii"), "send_pii": CONFIG.y("error_reporting.send_pii"),
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)), "traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)),

View File

@ -32,6 +32,7 @@ log_level: info
# Error reporting, sends stacktrace to sentry.beryju.org # Error reporting, sends stacktrace to sentry.beryju.org
error_reporting: error_reporting:
enabled: false enabled: false
sentry_dsn: https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8
environment: customer environment: customer
send_pii: false send_pii: false
sample_rate: 0.1 sample_rate: 0.1

View File

@ -34,7 +34,6 @@ from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import class_to_path, get_env from authentik.lib.utils.reflection import class_to_path, get_env
LOGGER = get_logger() LOGGER = get_logger()
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
class SentryWSMiddleware(BaseMiddleware): class SentryWSMiddleware(BaseMiddleware):
@ -71,7 +70,7 @@ def sentry_init(**sentry_init_kwargs):
kwargs.update(**sentry_init_kwargs) kwargs.update(**sentry_init_kwargs)
# pylint: disable=abstract-class-instantiated # pylint: disable=abstract-class-instantiated
sentry_sdk_init( sentry_sdk_init(
dsn=SENTRY_DSN, dsn=CONFIG.y("error_reporting.sentry_dsn"),
integrations=[ integrations=[
DjangoIntegration(transaction_style="function_name"), DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(), CeleryIntegration(),

View File

@ -38,7 +38,7 @@ func main() {
if config.Get().ErrorReporting.Enabled { if config.Get().ErrorReporting.Enabled {
err := sentry.Init(sentry.ClientOptions{ err := sentry.Init(sentry.ClientOptions{
Dsn: config.Get().ErrorReporting.DSN, Dsn: config.Get().ErrorReporting.SentryDSN,
AttachStacktrace: true, AttachStacktrace: true,
TracesSampler: sentryutils.SamplerFunc(config.Get().ErrorReporting.SampleRate), TracesSampler: sentryutils.SamplerFunc(config.Get().ErrorReporting.SampleRate),
Release: fmt.Sprintf("authentik@%s", constants.VERSION), Release: fmt.Sprintf("authentik@%s", constants.VERSION),

View File

@ -40,7 +40,6 @@ func defaultConfig() *Config {
LogLevel: "info", LogLevel: "info",
ErrorReporting: ErrorReportingConfig{ ErrorReporting: ErrorReportingConfig{
Enabled: false, Enabled: false,
DSN: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
SampleRate: 1, SampleRate: 1,
}, },
} }
@ -63,11 +62,11 @@ func (c *Config) Setup(paths ...string) {
func (c *Config) LoadConfig(path string) error { func (c *Config) LoadConfig(path string) error {
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
if err != nil { 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) err = yaml.Unmarshal(raw, c)
if err != nil { if err != nil {
return fmt.Errorf("Failed to parse YAML: %w", err) return fmt.Errorf("failed to parse YAML: %w", err)
} }
c.walkScheme(c) c.walkScheme(c)
log.WithField("path", path).Debug("Loaded config") log.WithField("path", path).Debug("Loaded config")

View File

@ -38,10 +38,10 @@ type PathsConfig struct {
} }
type ErrorReportingConfig struct { 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"` SentryDSN string `yaml:"sentry_dsn" env:"AUTHENTIK_ERROR_REPORTING__SENTRY_DSN"`
SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"` Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"`
DSN string SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"`
SampleRate float64 `yaml:"sample_rate" env:"AUTHENTIK_ERROR_REPORTING__SAMPLE_RATE"` SampleRate float64 `yaml:"sample_rate" env:"AUTHENTIK_ERROR_REPORTING__SAMPLE_RATE"`
} }

View File

@ -44,12 +44,11 @@ func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
} }
if globalConfig.ErrorReporting.Enabled { if globalConfig.ErrorReporting.Enabled {
dsn := "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
if !initialSetup { if !initialSetup {
l.WithField("env", globalConfig.ErrorReporting.Environment).Debug("Error reporting enabled") l.WithField("env", globalConfig.ErrorReporting.Environment).Debug("Error reporting enabled")
} }
err := sentry.Init(sentry.ClientOptions{ err := sentry.Init(sentry.ClientOptions{
Dsn: dsn, Dsn: globalConfig.ErrorReporting.SentryDsn,
Environment: globalConfig.ErrorReporting.Environment, Environment: globalConfig.ErrorReporting.Environment,
TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)), TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)),
Release: fmt.Sprintf("authentik@%s", constants.VERSION), Release: fmt.Sprintf("authentik@%s", constants.VERSION),

View File

@ -19,7 +19,7 @@ func TestSecret() string {
func MockConfig() api.Config { func MockConfig() api.Config {
return *api.NewConfig( return *api.NewConfig(
*api.NewErrorReportingConfig(false, "test", false, 0.0), *api.NewErrorReportingConfig(false, "https://foo.bar/9", "test", false, 0.0),
[]api.CapabilitiesEnum{}, []api.CapabilitiesEnum{},
100, 100,
100, 100,

View File

@ -3,8 +3,11 @@ package web
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"strconv"
"strings" "strings"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
@ -14,41 +17,62 @@ type SentryRequest struct {
DSN string `json:"dsn"` 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 { if !config.Get().ErrorReporting.Enabled {
ws.log.Debug("error reporting disabled") ws.log.Debug("error reporting disabled")
rw.WriteHeader(http.StatusBadRequest) return fallbackHandler
return
} }
fb := &bytes.Buffer{} dsn, err := url.Parse(config.Get().ErrorReporting.SentryDSN)
_, err := io.Copy(fb, r.Body)
if err != nil { if err != nil {
ws.log.Debug("failed to read body") ws.log.WithError(err).Warning("invalid sentry DSN")
rw.WriteHeader(http.StatusBadRequest) return fallbackHandler
return
} }
lines := strings.Split(fb.String(), "\n") projectId, err := strconv.Atoi(strings.TrimPrefix(dsn.Path, "/"))
if len(lines) < 1 {
rw.WriteHeader(http.StatusBadRequest)
return
}
sd := SentryRequest{}
err = json.Unmarshal([]byte(lines[0]), &sd)
if err != nil { if err != nil {
ws.log.WithError(err).Warning("failed to parse sentry request") ws.log.WithError(err).Warning("failed to get sentry project id")
rw.WriteHeader(http.StatusBadRequest) return fallbackHandler
return
} }
if sd.DSN != config.Get().ErrorReporting.DSN { return func(rw http.ResponseWriter, r *http.Request) {
ws.log.WithField("have", sd.DSN).WithField("expected", config.Get().ErrorReporting.DSN).Debug("invalid DSN") fb := &bytes.Buffer{}
rw.WriteHeader(http.StatusBadRequest) _, err := io.Copy(fb, r.Body)
return 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)
} }

View File

@ -53,7 +53,7 @@ func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
} }
func (ws *WebServer) configureRoutes() { 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() { func (ws *WebServer) Start() {

View File

@ -27162,6 +27162,9 @@ components:
enabled: enabled:
type: boolean type: boolean
readOnly: true readOnly: true
sentry_dsn:
type: string
readOnly: true
environment: environment:
type: string type: string
readOnly: true readOnly: true
@ -27176,6 +27179,7 @@ components:
- enabled - enabled
- environment - environment
- send_pii - send_pii
- sentry_dsn
- traces_sample_rate - traces_sample_rate
Event: Event:
type: object type: object

View File

@ -14,7 +14,7 @@ export async function configureSentry(canDoPpi = false): Promise<Config> {
const cfg = await config(); const cfg = await config();
if (cfg.errorReporting.enabled) { if (cfg.errorReporting.enabled) {
Sentry.init({ Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", dsn: cfg.errorReporting.sentryDsn,
ignoreErrors: [ ignoreErrors: [
/network/gi, /network/gi,
/fetch/gi, /fetch/gi,

View File

@ -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. 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` - `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` - `AUTHENTIK_ERROR_REPORTING__SEND_PII`