New tracing system (#5388)

* update tracing config definitions

* new tracing system

* performance improvements

* only configure tracing in envoy if it is enabled in pomerium

* [tracing] refactor to use custom extension for trace id editing (#5420)

refactor to use custom extension for trace id editing

* set default tracing sample rate to 1.0

* fix proxy service http middleware

* improve some existing auth related traces

* test fixes

* bump envoyproxy/go-control-plane

* code cleanup

* test fixes

* Fix missing spans for well-known endpoints

* import extension apis from pomerium/envoy-custom
This commit is contained in:
Joe Kralicky 2025-01-21 13:26:32 -05:00 committed by GitHub
parent 832742648d
commit 396c35b6b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 6096 additions and 1946 deletions

View file

@ -4,11 +4,17 @@
package authenticateflow
import (
"context"
"fmt"
"time"
oteltrace "go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/stats"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/grpc"
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/identity"
@ -33,3 +39,23 @@ func populateUserFromClaims(u *user.User, claims map[string]any) {
u.Claims[k] = vs
}
}
var outboundDatabrokerTraceClientOpts = []trace.ClientStatsHandlerOption{
trace.WithStatsInterceptor(ignoreNotFoundErrors),
}
func ignoreNotFoundErrors(ctx context.Context, rs stats.RPCStats) stats.RPCStats {
if end, ok := rs.(*stats.End); ok && end.IsClient() {
if status.Code(end.Error) == codes.NotFound {
oteltrace.SpanFromContext(ctx).AddEvent("status code: NotFound")
return &stats.End{
Client: end.Client,
BeginTime: end.BeginTime,
EndTime: end.EndTime,
Trailer: end.Trailer,
Error: nil,
}
}
}
return rs
}

View file

@ -9,7 +9,11 @@ import (
"net/url"
"time"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
oteltrace "go.opentelemetry.io/otel/trace"
"golang.org/x/oauth2"
googlegrpc "google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/pomerium/pomerium/config"
@ -19,6 +23,7 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -56,7 +61,7 @@ type Stateful struct {
// NewStateful initializes the authentication flow for the given configuration
// and session store.
func NewStateful(ctx context.Context, cfg *config.Config, sessionStore sessions.SessionStore) (*Stateful, error) {
func NewStateful(ctx context.Context, tracerProvider oteltrace.TracerProvider, cfg *config.Config, sessionStore sessions.SessionStore) (*Stateful, error) {
s := &Stateful{
sessionDuration: cfg.Options.CookieExpire,
sessionStore: sessionStore,
@ -94,7 +99,10 @@ func NewStateful(ctx context.Context, cfg *config.Config, sessionStore sessions.
InstallationID: cfg.Options.InstallationID,
ServiceName: cfg.Options.Services,
SignedJWTKey: s.sharedKey,
})
}, googlegrpc.WithStatsHandler(trace.NewClientStatsHandler(
otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(tracerProvider)),
outboundDatabrokerTraceClientOpts...,
)))
if err != nil {
return nil, err
}
@ -316,7 +324,7 @@ func (s *Stateful) LogAuthenticateEvent(*http.Request) {}
// AuthenticateSignInURL returns a URL to redirect the user to the authenticate
// domain.
func (s *Stateful) AuthenticateSignInURL(
_ context.Context, queryParams url.Values, redirectURL *url.URL, idpID string,
ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string,
) (string, error) {
signinURL := s.authenticateURL.ResolveReference(&url.URL{
Path: "/.pomerium/sign_in",
@ -327,6 +335,7 @@ func (s *Stateful) AuthenticateSignInURL(
}
queryParams.Set(urlutil.QueryRedirectURI, redirectURL.String())
queryParams.Set(urlutil.QueryIdentityProviderID, idpID)
otel.GetTextMapPropagator().Inject(ctx, trace.PomeriumURLQueryCarrier(queryParams))
signinURL.RawQuery = queryParams.Encode()
redirectTo := urlutil.NewSignedURL(s.sharedKey, signinURL).String()

View file

@ -15,6 +15,7 @@ import (
"github.com/go-jose/go-jose/v3/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.uber.org/mock/gomock"
"golang.org/x/oauth2"
"google.golang.org/grpc"
@ -69,7 +70,7 @@ func TestStatefulSignIn(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sessionStore := &mstore.Store{SaveError: tt.saveError}
flow, err := NewStateful(context.Background(), &config.Config{Options: opts}, sessionStore)
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, sessionStore)
if err != nil {
t.Fatal(err)
}
@ -123,12 +124,12 @@ func TestStatefulAuthenticateSignInURL(t *testing.T) {
opts.AuthenticateURLString = "https://authenticate.example.com"
key := cryptutil.NewKey()
opts.SharedKey = base64.StdEncoding.EncodeToString(key)
flow, err := NewStateful(context.Background(), &config.Config{Options: opts}, nil)
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, nil)
require.NoError(t, err)
t.Run("NilQueryParams", func(t *testing.T) {
redirectURL := &url.URL{Scheme: "https", Host: "example.com"}
u, err := flow.AuthenticateSignInURL(nil, nil, redirectURL, "fake-idp-id")
u, err := flow.AuthenticateSignInURL(context.Background(), nil, redirectURL, "fake-idp-id")
assert.NoError(t, err)
parsed, _ := url.Parse(u)
assert.NoError(t, urlutil.NewSignedURL(key, parsed).Validate())
@ -143,7 +144,7 @@ func TestStatefulAuthenticateSignInURL(t *testing.T) {
redirectURL := &url.URL{Scheme: "https", Host: "example.com"}
q := url.Values{}
q.Set("foo", "bar")
u, err := flow.AuthenticateSignInURL(nil, q, redirectURL, "fake-idp-id")
u, err := flow.AuthenticateSignInURL(context.Background(), q, redirectURL, "fake-idp-id")
assert.NoError(t, err)
parsed, _ := url.Parse(u)
assert.NoError(t, urlutil.NewSignedURL(key, parsed).Validate())
@ -238,7 +239,7 @@ func TestStatefulCallback(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flow, err := NewStateful(context.Background(), &config.Config{Options: opts}, tt.sessionStore)
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, tt.sessionStore)
if err != nil {
t.Fatal(err)
}
@ -289,7 +290,7 @@ func TestStatefulCallback(t *testing.T) {
func TestStatefulRevokeSession(t *testing.T) {
opts := config.NewDefaultOptions()
flow, err := NewStateful(context.Background(), &config.Config{Options: opts}, nil)
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, nil)
require.NoError(t, err)
ctrl := gomock.NewController(t)
@ -367,7 +368,7 @@ func TestPersistSession(t *testing.T) {
opts := config.NewDefaultOptions()
opts.CookieExpire = 4 * time.Hour
flow, err := NewStateful(context.Background(), &config.Config{Options: opts}, nil)
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, nil)
require.NoError(t, err)
ctrl := gomock.NewController(t)

View file

@ -10,6 +10,7 @@ import (
"github.com/go-jose/go-jose/v3"
"golang.org/x/oauth2"
googlegrpc "google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
"github.com/pomerium/pomerium/authenticate/events"
@ -20,6 +21,7 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -29,6 +31,9 @@ import (
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/hpke"
"github.com/pomerium/pomerium/pkg/identity"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
oteltrace "go.opentelemetry.io/otel/trace"
)
// Stateless implements the stateless authentication flow. In this flow, the
@ -56,18 +61,21 @@ type Stateless struct {
dataBrokerClient databroker.DataBrokerServiceClient
getIdentityProvider func(options *config.Options, idpID string) (identity.Authenticator, error)
getIdentityProvider func(ctx context.Context, tracerProvider oteltrace.TracerProvider, options *config.Options, idpID string) (identity.Authenticator, error)
profileTrimFn func(*identitypb.Profile)
authEventFn events.AuthEventFn
tracerProvider oteltrace.TracerProvider
}
// NewStateless initializes the authentication flow for the given
// configuration, session store, and additional options.
func NewStateless(
ctx context.Context,
tracerProvider oteltrace.TracerProvider,
cfg *config.Config,
sessionStore sessions.SessionStore,
getIdentityProvider func(options *config.Options, idpID string) (identity.Authenticator, error),
getIdentityProvider func(ctx context.Context, tracerProvider oteltrace.TracerProvider, options *config.Options, idpID string) (identity.Authenticator, error),
profileTrimFn func(*identitypb.Profile),
authEventFn events.AuthEventFn,
) (*Stateless, error) {
@ -77,6 +85,7 @@ func NewStateless(
getIdentityProvider: getIdentityProvider,
profileTrimFn: profileTrimFn,
authEventFn: authEventFn,
tracerProvider: tracerProvider,
}
var err error
@ -137,7 +146,10 @@ func NewStateless(
InstallationID: cfg.Options.InstallationID,
ServiceName: cfg.Options.Services,
SignedJWTKey: sharedKey,
})
}, googlegrpc.WithStatsHandler(trace.NewClientStatsHandler(
otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(tracerProvider)),
outboundDatabrokerTraceClientOpts...,
)))
if err != nil {
return nil, err
}
@ -154,7 +166,7 @@ func (s *Stateless) VerifySession(ctx context.Context, r *http.Request, _ *sessi
return fmt.Errorf("identity profile load error: %w", err)
}
authenticator, err := s.getIdentityProvider(s.options, profile.GetProviderId())
authenticator, err := s.getIdentityProvider(ctx, s.tracerProvider, s.options, profile.GetProviderId())
if err != nil {
return fmt.Errorf("couldn't get identity provider: %w", err)
}
@ -355,6 +367,7 @@ func (s *Stateless) AuthenticateSignInURL(
for k, v := range queryParams {
q[k] = v
}
otel.GetTextMapPropagator().Inject(ctx, trace.PomeriumURLQueryCarrier(q))
authenticateURLWithParams.RawQuery = q.Encode()
return urlutil.SignInURL(