feat: add OTEL instrumentation for authentication and handlers (#25296)

Signed-off-by: Mike Cutsail <mcutsail15@apple.com>
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
This commit is contained in:
Mike Cutsail
2026-02-10 05:31:55 -08:00
committed by GitHub
parent 2c5f7317a5
commit 2793097480
6 changed files with 156 additions and 27 deletions

View File

@@ -44,8 +44,11 @@ import (
log "github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
otel_codes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -166,6 +169,9 @@ var (
enableGRPCTimeHistogram = true
)
// OpenTelemetry tracer for this package
var tracer trace.Tracer
func init() {
maxConcurrentLoginRequestsCount = env.ParseNumFromEnv(maxConcurrentLoginRequestsCountEnv, maxConcurrentLoginRequestsCount, 0, math.MaxInt32)
replicasCount = env.ParseNumFromEnv(replicasCountEnv, replicasCount, 0, math.MaxInt32)
@@ -173,6 +179,7 @@ func init() {
maxConcurrentLoginRequestsCount = maxConcurrentLoginRequestsCount / replicasCount
}
enableGRPCTimeHistogram = env.ParseBoolFromEnv(common.EnvEnableGRPCTimeHistogramEnv, false)
tracer = otel.Tracer("github.com/argoproj/argo-cd/v3/server")
}
// ArgoCDServer is the API server for Argo CD
@@ -1164,8 +1171,8 @@ func (server *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWeb
Handler: &handlerSwitcher{
handler: mux,
urlToHandler: map[string]http.Handler{
"/api/badge": badge.NewHandler(server.AppClientset, server.settingsMgr, server.Namespace, server.ApplicationNamespaces),
common.LogoutEndpoint: logout.NewHandler(server.settingsMgr, server.sessionMgr, server.RootPath, server.BaseHRef),
"/api/badge": otelhttp.NewHandler(badge.NewHandler(server.AppClientset, server.settingsMgr, server.Namespace, server.ApplicationNamespaces), "server.ArgoCDServer/badge"),
common.LogoutEndpoint: otelhttp.NewHandler(logout.NewHandler(server.settingsMgr, server.sessionMgr, server.RootPath, server.BaseHRef), "server.ArgoCDServer/logout"),
},
contentTypeToHandler: map[string]http.Handler{
"application/grpc-web+proto": grpcWebHandler,
@@ -1293,7 +1300,7 @@ func registerExtensions(mux *http.ServeMux, a *ArgoCDServer, metricsReg HTTPMetr
extHandler := http.HandlerFunc(a.extensionManager.CallExtension())
authMiddleware := a.sessionMgr.AuthMiddlewareFunc(a.DisableAuth, a.settings.IsSSOConfigured(), a.ssoClientApp)
// auth middleware ensures that requests to all extensions are authenticated first
mux.Handle(extension.URLPrefix+"/", authMiddleware(extHandler))
mux.Handle(extension.URLPrefix+"/", otelhttp.NewHandler(authMiddleware(extHandler), "server.ArgoCDServer/extensions"))
a.extensionManager.AddMetricsRegistry(metricsReg)
@@ -1351,9 +1358,10 @@ func (server *ArgoCDServer) registerDexHandlers(mux *http.ServeMux) {
return
}
// Run dex OpenID Connect Identity Provider behind a reverse proxy (served at /api/dex)
mux.HandleFunc(common.DexAPIEndpoint+"/", dexutil.NewDexHTTPReverseProxy(server.DexServerAddr, server.BaseHRef, server.DexTLSConfig))
mux.HandleFunc(common.LoginEndpoint, server.ssoClientApp.HandleLogin)
mux.HandleFunc(common.CallbackEndpoint, server.ssoClientApp.HandleCallback)
mux.Handle(common.DexAPIEndpoint+"/", otelhttp.NewHandler(http.HandlerFunc(dexutil.NewDexHTTPReverseProxy(server.DexServerAddr, server.BaseHRef, server.DexTLSConfig)), "server.dex/Proxy"))
mux.Handle(common.LoginEndpoint, otelhttp.NewHandler(http.HandlerFunc(server.ssoClientApp.HandleLogin), "server.ClientApp/HandleLogin"))
mux.Handle(common.CallbackEndpoint, otelhttp.NewHandler(http.HandlerFunc(server.ssoClientApp.HandleCallback), "server.ClientApp/HandleCallback"))
}
// newRedirectServer returns an HTTP server which does a 307 redirect to the HTTPS server
@@ -1510,6 +1518,9 @@ func replaceBaseHRef(data string, replaceWith string) string {
// Authenticate checks for the presence of a valid token when accessing server-side resources.
func (server *ArgoCDServer) Authenticate(ctx context.Context) (context.Context, error) {
var span trace.Span
ctx, span = tracer.Start(ctx, "server.ArgoCDServer.Authenticate")
defer span.End()
if server.DisableAuth {
return ctx, nil
}
@@ -1549,18 +1560,24 @@ func (server *ArgoCDServer) Authenticate(ctx context.Context) (context.Context,
// getClaims extracts, validates and refreshes a JWT token from an incoming request context.
func (server *ArgoCDServer) getClaims(ctx context.Context) (jwt.Claims, string, error) {
var span trace.Span
ctx, span = tracer.Start(ctx, "server.ArgoCDServer.getClaims")
defer span.End()
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
span.SetStatus(otel_codes.Error, ErrNoSession.Error())
return nil, "", ErrNoSession
}
tokenString := getToken(md)
if tokenString == "" {
span.SetStatus(otel_codes.Error, ErrNoSession.Error())
return nil, "", ErrNoSession
}
// A valid argocd-issued token is automatically refreshed here prior to expiration.
// OIDC tokens will be verified but will not be refreshed here.
claims, newToken, err := server.sessionMgr.VerifyToken(ctx, tokenString)
if err != nil {
span.SetStatus(otel_codes.Error, err.Error())
return claims, "", status.Errorf(codes.Unauthenticated, "invalid session: %v", err)
}