Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configure sentry for happy api #2350

Merged
merged 13 commits into from
Aug 28, 2023
3 changes: 3 additions & 0 deletions api/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ auth:
api:
port: 3001
log_level: info
deployment_stage: "{{.DEPLOYMENT_STAGE}}"
database:
driver: postgres
data_source_name: host={{.HAPPY_DATABASE_HOST}} user={{.HAPPY_DATABASE_USER}} password={{.HAPPY_DATABASE_PASSWORD}} port={{.HAPPY_DATABASE_PORT}} dbname={{.HAPPY_DATABASE_NAME}}
log_level: silent
tfe:
token: "{{.TFE_TOKEN}}"
sentry:
dsn: "{{.sentry_dsn}}"
4 changes: 3 additions & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/chanzuckerberg/happy/shared v0.0.0
github.com/coreos/go-oidc/v3 v3.6.0
github.com/getsentry/sentry-go v0.23.0
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.15.1
github.com/gofiber/contrib/fibersentry v1.0.4
github.com/gofiber/fiber/v2 v2.49.0
github.com/gofiber/swagger v0.1.12
github.com/golang-jwt/jwt v3.2.2+incompatible
Expand All @@ -26,6 +28,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/swaggo/swag v1.16.1
github.com/valyala/fasthttp v1.48.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.2
gorm.io/driver/sqlite v1.5.2
Expand Down Expand Up @@ -171,7 +174,6 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
Expand Down
6 changes: 6 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,11 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE=
github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down Expand Up @@ -240,6 +243,8 @@ github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2r
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/gofiber/contrib/fibersentry v1.0.4 h1:RjmWbv3iU9D9ApWig/5QGHX+8xqD3qZhzcQlTPBMW0w=
github.com/gofiber/contrib/fibersentry v1.0.4/go.mod h1:UuoYCuWcxLmU0vF8hwKl3CyzbeZ9UUj1T+rW1EsP8/I=
github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
github.com/gofiber/fiber/v2 v2.49.0 h1:xBVG2c66GDcWfww56xHvMn52Q0XX7UrSvjj6MD8/5EE=
github.com/gofiber/fiber/v2 v2.49.0/go.mod h1:oxpt7wQaEYgdDmq7nMxCGhilYicBLFnZ+jQSJcQDlSE=
Expand Down Expand Up @@ -474,6 +479,7 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
17 changes: 17 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package main

import (
"context"
"time"

_ "github.com/chanzuckerberg/happy/api/docs" // import API docs
"github.com/chanzuckerberg/happy/api/pkg/api"
"github.com/chanzuckerberg/happy/api/pkg/setup"
sentry "github.com/getsentry/sentry-go"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
Expand All @@ -19,6 +21,21 @@ func exec(ctx context.Context) error {
}
logrus.Info("Running with configuration:\n", string(m))

err = sentry.Init(sentry.ClientOptions{
Dsn: cfg.Sentry.DSN,
Environment: cfg.Api.DeploymentStage,
EnableTracing: true,
TracesSampleRate: 1.0,
})
if err == nil {
logrus.Info("Sentry enabled for environment: ", cfg.Api.DeploymentStage)
// Flush buffered events before the program terminates.
// Set the timeout to the maximum duration the program can afford to wait.
defer sentry.Flush(2 * time.Second)
} else {
logrus.Info("Sentry disabled for environment: ", cfg.Api.DeploymentStage)
}

return api.MakeApp(ctx, cfg).Listen()
}

Expand Down
20 changes: 20 additions & 0 deletions api/pkg/api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/chanzuckerberg/happy/api/pkg/dbutil"
"github.com/chanzuckerberg/happy/api/pkg/request"
"github.com/chanzuckerberg/happy/api/pkg/setup"
"github.com/getsentry/sentry-go"
"github.com/gofiber/contrib/fibersentry"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
Expand Down Expand Up @@ -75,6 +77,24 @@ func MakeApp(ctx context.Context, cfg *setup.Configuration) *APIApplication {
v1.Use(request.MakeAuth(request.MakeMultiOIDCVerifier(verifiers...)))
}

v1.Use(fibersentry.New(fibersentry.Config{
Repanic: true,
WaitForDelivery: true,
}))
v1.Use(func(c *fiber.Ctx) error {
userEmail := c.Locals("oidc_claims_email")
if userEmail != nil {
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetUser(sentry.User{Email: userEmail.(string)})
})
}

txn := sentry.StartSpan(c.Context(), c.Method(), sentry.WithTransactionName(c.Path()))
defer txn.Finish()
res := c.Next()
return res
})

RegisterConfigV1(v1, MakeConfigHandler(cmd.MakeConfig(apiApp.DB)))
RegisterStackListV1(v1, MakeStackHandler(cmd.MakeStack(apiApp.DB)))

Expand Down
21 changes: 15 additions & 6 deletions api/pkg/request/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,24 @@ func MakeOIDCProvider(ctx context.Context, issuerURL, clientID string, claimsVer
}, nil
}

func validateAuthHeader(ctx context.Context, authHeader string, verifier OIDCVerifier) error {
func validateAuthHeader(c *fiber.Ctx, authHeader string, verifier OIDCVerifier) error {
rawIDToken := stripBearerPrefixFromTokenString(authHeader)
_, err := verifier.Verify(ctx, rawIDToken)
token, err := verifier.Verify(c.Context(), rawIDToken)
if err != nil {
return errors.Wrap(err, "unable to verify ID token")
}
// TODO: once we have some common patterns of access, extra these properties
// from the ID token here and attach them to the request using
// fiber.Ctx.Locals(key, value)

var claims struct {
Email string `json:"email"`
}
err = token.Claims(&claims)
if err != nil {
return err
}

c.Locals("oidc_subject", token.Subject)
c.Locals("oidc_claims_email", claims.Email)

return nil
}

Expand All @@ -170,7 +179,7 @@ func MakeAuth(verifier OIDCVerifier) fiber.Handler {
return response.AuthErrorResponse(c, "missing auth header")
}

err := validateAuthHeader(c.Context(), authHeader, verifier)
err := validateAuthHeader(c, authHeader, verifier)
if err != nil {
return response.AuthErrorResponse(c, err.Error())
}
Expand Down
11 changes: 8 additions & 3 deletions api/pkg/request/auth_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package request

import (
"context"
"fmt"
"testing"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)

func TestStripBearer(t *testing.T) {
Expand Down Expand Up @@ -75,7 +76,9 @@ func TestValidateAuthHeaderNoErrors(t *testing.T) {
tc := testcase
t.Run(tc.authHeader, func(t *testing.T) {
t.Parallel()
err := validateAuthHeader(context.Background(), tc.authHeader, tc.verifier)
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
err := validateAuthHeader(ctx, tc.authHeader, tc.verifier)
r.NoError(err)
})
}
Expand Down Expand Up @@ -103,7 +106,9 @@ func TestValidateAuthHeaderErrors(t *testing.T) {
tc := testCase
t.Run(tc.authHeader, func(t *testing.T) {
t.Parallel()
err := validateAuthHeader(context.Background(), tc.authHeader, tc.verifier)
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
err := validateAuthHeader(ctx, tc.authHeader, tc.verifier)
r.Error(err)
})
}
Expand Down
10 changes: 8 additions & 2 deletions api/pkg/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ type AuthConfiguration struct {
}

type ApiConfiguration struct {
Port uint `mapstructure:"port"`
LogLevel string `mapstructure:"log_level"`
Port uint `mapstructure:"port"`
LogLevel string `mapstructure:"log_level"`
DeploymentStage string `mapstructure:"deployment_stage"`
}

type DBDriver string
Expand All @@ -75,11 +76,16 @@ type TFEConfiguration struct {
Token string `mapstructure:"token"`
}

type SentryConfiguration struct {
DSN string `mapstructure:"dsn"`
}

type Configuration struct {
Auth AuthConfiguration `mapstructure:"auth"`
Api ApiConfiguration `mapstructure:"api"`
Database DatabaseConfiguration `mapstructure:"database"`
TFE TFEConfiguration `mapstructure:"tfe"`
Sentry SentryConfiguration `mapstructure:"sentry"`
}

const defaultConfigYamlDir = "./"
Expand Down
Loading