mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
[public-api] Measure incoming JWT Sessions - WEB-102 (#17345)
* retest * retest * [installer] Add cookie name to config * Fix * retest * [installer] Add cookie name to config * [public-api] Measure incoming JWT Sessions * fix * Fix * Fix * fix * retest
This commit is contained in:
parent
3a3d99edd3
commit
0dc46c5bcc
@ -11,6 +11,7 @@ const (
|
|||||||
OIDCServiceEnabledFlag = "oidcServiceEnabled"
|
OIDCServiceEnabledFlag = "oidcServiceEnabled"
|
||||||
SupervisorPersistServerAPIChannelWhenStartFlag = "supervisor_persist_serverapi_channel_when_start"
|
SupervisorPersistServerAPIChannelWhenStartFlag = "supervisor_persist_serverapi_channel_when_start"
|
||||||
SupervisorUsePublicAPIFlag = "supervisor_experimental_publicapi"
|
SupervisorUsePublicAPIFlag = "supervisor_experimental_publicapi"
|
||||||
|
JWTSessionsEnabledFlag = "jwtSessionCookieEnabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsPersonalAccessTokensEnabled(ctx context.Context, client Client, attributes Attributes) bool {
|
func IsPersonalAccessTokensEnabled(ctx context.Context, client Client, attributes Attributes) bool {
|
||||||
@ -28,3 +29,7 @@ func SupervisorPersistServerAPIChannelWhenStart(ctx context.Context, client Clie
|
|||||||
func SupervisorUsePublicAPI(ctx context.Context, client Client, attributes Attributes) bool {
|
func SupervisorUsePublicAPI(ctx context.Context, client Client, attributes Attributes) bool {
|
||||||
return client.GetBoolValue(ctx, SupervisorUsePublicAPIFlag, false, attributes)
|
return client.GetBoolValue(ctx, SupervisorUsePublicAPIFlag, false, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JWTSessionsEnabled(ctx context.Context, client Client, attributes Attributes) bool {
|
||||||
|
return client.GetBoolValue(ctx, JWTSessionsEnabledFlag, false, attributes)
|
||||||
|
}
|
||||||
|
|||||||
28
components/public-api-server/pkg/auth/metrics.go
Normal file
28
components/public-api-server/pkg/auth/metrics.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
|
||||||
|
// Licensed under the GNU Affero General Public License (AGPL).
|
||||||
|
// See License.AGPL.txt in the project root for license information.
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func reportRequestWithJWT(jwtPresent bool) {
|
||||||
|
requestsWithJWTSessionsTotal.WithLabelValues(strconv.FormatBool(jwtPresent)).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
requestsWithJWTSessionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "gitpod",
|
||||||
|
Subsystem: "public_api",
|
||||||
|
Name: "requests_with_jwt_sessions_total",
|
||||||
|
Help: "Count of sessions with, or without JWT sessions",
|
||||||
|
}, []string{"with_jwt"})
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterMetrics(registry *prometheus.Registry) {
|
||||||
|
registry.MustRegister(requestsWithJWTSessionsTotal)
|
||||||
|
}
|
||||||
@ -5,9 +5,14 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bufbuild/connect-go"
|
||||||
|
"github.com/gitpod-io/gitpod/common-go/experiments"
|
||||||
|
"github.com/gitpod-io/gitpod/common-go/log"
|
||||||
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws"
|
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -41,3 +46,58 @@ func VerifySessionJWT(token string, verifier jws.Verifier, expectedIssuer string
|
|||||||
|
|
||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewJWTCookieInterceptor(exp experiments.Client, cookieName string, expectedIssuer string, verifier jws.Verifier) connect.UnaryInterceptorFunc {
|
||||||
|
interceptor := func(next connect.UnaryFunc) connect.UnaryFunc {
|
||||||
|
|
||||||
|
return connect.UnaryFunc(func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
|
||||||
|
validJWT := false
|
||||||
|
defer func() {
|
||||||
|
reportRequestWithJWT(validJWT)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if req.Spec().IsClient {
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := TokenFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !experiments.JWTSessionsEnabled(ctx, exp, experiments.Attributes{}) {
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Type != CookieTokenType {
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtSessionCookie, err := cookieFromString(token.Value, cookieName)
|
||||||
|
if err != nil {
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := VerifySessionJWT(jwtSessionCookie.Value, verifier, expectedIssuer)
|
||||||
|
if err != nil {
|
||||||
|
log.Extract(ctx).WithError(err).Warnf("Failed to verify JWT session token")
|
||||||
|
return next(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
validJWT = claims != nil
|
||||||
|
|
||||||
|
return next(ctx, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return connect.UnaryInterceptorFunc(interceptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cookieFromString(rawCookieHeader, name string) (*http.Cookie, error) {
|
||||||
|
// To access the cookie as an http.Cookie, we sadly have to construct a request with the appropriate header such
|
||||||
|
// that we can then extract the cookie.
|
||||||
|
header := http.Header{}
|
||||||
|
header.Add("Cookie", rawCookieHeader)
|
||||||
|
req := http.Request{Header: header}
|
||||||
|
|
||||||
|
return req.Cookie(name)
|
||||||
|
}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ func Start(logger *logrus.Entry, version string, cfg *config.Configuration) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup JWS Keyset: %w", err)
|
return fmt.Errorf("failed to setup JWS Keyset: %w", err)
|
||||||
}
|
}
|
||||||
_, err = jws.NewRSA256(keyset)
|
rsa256, err := jws.NewRSA256(keyset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup jws.RSA256: %w", err)
|
return fmt.Errorf("failed to setup jws.RSA256: %w", err)
|
||||||
}
|
}
|
||||||
@ -139,13 +139,15 @@ func Start(logger *logrus.Entry, version string, cfg *config.Configuration) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if registerErr := register(srv, ®isterDependencies{
|
if registerErr := register(srv, ®isterDependencies{
|
||||||
connPool: connPool,
|
connPool: connPool,
|
||||||
expClient: expClient,
|
expClient: expClient,
|
||||||
dbConn: dbConn,
|
dbConn: dbConn,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
cipher: cipherSet,
|
cipher: cipherSet,
|
||||||
oidcService: oidcService,
|
oidcService: oidcService,
|
||||||
idpService: idpService,
|
idpService: idpService,
|
||||||
|
authCfg: cfg.Auth,
|
||||||
|
sessionVerifier: rsa256,
|
||||||
}); registerErr != nil {
|
}); registerErr != nil {
|
||||||
return fmt.Errorf("failed to register services: %w", registerErr)
|
return fmt.Errorf("failed to register services: %w", registerErr)
|
||||||
}
|
}
|
||||||
@ -165,10 +167,14 @@ type registerDependencies struct {
|
|||||||
cipher db.Cipher
|
cipher db.Cipher
|
||||||
oidcService *oidc.Service
|
oidcService *oidc.Service
|
||||||
idpService *identityprovider.Service
|
idpService *identityprovider.Service
|
||||||
|
|
||||||
|
sessionVerifier jws.SignerVerifier
|
||||||
|
authCfg config.AuthConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(srv *baseserver.Server, deps *registerDependencies) error {
|
func register(srv *baseserver.Server, deps *registerDependencies) error {
|
||||||
proxy.RegisterMetrics(srv.MetricsRegistry())
|
proxy.RegisterMetrics(srv.MetricsRegistry())
|
||||||
|
auth.RegisterMetrics(srv.MetricsRegistry())
|
||||||
|
|
||||||
connectMetrics := NewConnectMetrics()
|
connectMetrics := NewConnectMetrics()
|
||||||
err := connectMetrics.Register(srv.MetricsRegistry())
|
err := connectMetrics.Register(srv.MetricsRegistry())
|
||||||
@ -186,6 +192,7 @@ func register(srv *baseserver.Server, deps *registerDependencies) error {
|
|||||||
NewLogInterceptor(log.Log),
|
NewLogInterceptor(log.Log),
|
||||||
auth.NewServerInterceptor(),
|
auth.NewServerInterceptor(),
|
||||||
origin.NewInterceptor(),
|
origin.NewInterceptor(),
|
||||||
|
auth.NewJWTCookieInterceptor(deps.expClient, deps.authCfg.Session.Cookie.Name, deps.authCfg.Session.Issuer, deps.sessionVerifier),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user