From 5488de4b1b83e7e21840cee759134dfd4c36e0ea Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Fri, 1 Nov 2024 11:49:53 +0100 Subject: [PATCH 1/2] feat: track some errors for sqa --- cmd/jwt/root.go | 4 +++- internal/auth/scheme.go | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/jwt/root.go b/cmd/jwt/root.go index 7f102329..a9208ef7 100644 --- a/cmd/jwt/root.go +++ b/cmd/jwt/root.go @@ -101,7 +101,9 @@ func NewJWTCmd() (cmd *cobra.Command) { } if signingMethod == nil || key == nil { - log.Fatal(errors.New("algorithm and secret are required")) + err = errors.New("algorithm and secret are required") + analyticsx.TrackError(ctx, tracer, err) + log.Fatal(err) return } diff --git a/internal/auth/scheme.go b/internal/auth/scheme.go index 1dae3a41..480f64d1 100644 --- a/internal/auth/scheme.go +++ b/internal/auth/scheme.go @@ -1,6 +1,12 @@ package auth -import "errors" +import ( + "context" + "errors" + + "github.com/cerberauth/x/analyticsx" + "go.opentelemetry.io/otel" +) type SchemeName string @@ -25,7 +31,9 @@ func (s *SchemeName) Set(v string) error { *s = SchemeName(v) return nil default: - return errors.New(`must be one of "basic", "bearer", "digest", "oauth", "privateToken"`) + err := errors.New(`must be one of "basic", "bearer", "digest", "oauth", "privateToken"`) + analyticsx.TrackError(context.TODO(), otel.Tracer("jwt"), err) + return err } } From 513da1ecfcecd189526f5f82d1964a09a0533d56 Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Wed, 6 Nov 2024 00:47:39 +0100 Subject: [PATCH 2/2] feat: add more sqa insights --- api/curl.go | 23 +++++------- api/graphql.go | 24 ++++++------- api/handler.go | 7 +++- api/openapi.go | 35 +++++++++--------- cmd/discover/api.go | 24 +++++++------ cmd/discover/domain.go | 24 +++++++------ cmd/discover/root.go | 3 ++ cmd/jwt/root.go | 26 +++++++++----- cmd/root.go | 3 +- cmd/scan/curl.go | 29 +++++++-------- cmd/scan/graphql.go | 27 +++++++------- cmd/scan/openapi.go | 33 +++++++++-------- cmd/scan/root.go | 3 ++ go.mod | 26 +++++++------- go.sum | 52 ++++++++++++++------------- internal/analytics/analytics.go | 10 ++---- internal/analytics/scan_report.go | 17 +++++++++ internal/auth/oauth.go | 2 +- internal/auth/oauth_test.go | 7 ++-- internal/auth/scheme.go | 32 ++++------------- internal/auth/scheme_test.go | 17 +-------- internal/auth/security_scheme.go | 13 +++++++ internal/auth/security_scheme_test.go | 50 ++++++++++++++++++++++++++ internal/cmd/analytics.go | 18 ---------- report/issue_report.go | 11 ++++++ report/report.go | 4 +++ scan/scan.go | 30 ++++++++++++++-- scan/scan_test.go | 19 +++++----- 28 files changed, 330 insertions(+), 239 deletions(-) create mode 100644 internal/analytics/scan_report.go create mode 100644 internal/auth/security_scheme_test.go delete mode 100644 internal/cmd/analytics.go diff --git a/api/curl.go b/api/curl.go index 3cf59b1d..da702744 100644 --- a/api/curl.go +++ b/api/curl.go @@ -3,13 +3,12 @@ package api import ( "net/http" + "github.com/cerberauth/vulnapi/internal/analytics" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/gin-gonic/gin" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) type NewURLScanRequest struct { @@ -20,17 +19,14 @@ type NewURLScanRequest struct { Opts *ScanOptions `json:"options"` } -var serverApiUrlTracer = otel.Tracer("server/api/url") - func (h *Handler) ScanURL(ctx *gin.Context) { var form NewURLScanRequest if err := ctx.ShouldBindJSON(&form); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - analyticsx.TrackEvent(ctx, serverApiUrlTracer, "Scan URL", []attribute.KeyValue{ - attribute.String("method", form.Method), - }) + traceCtx, span := tracer.Start(ctx.Request.Context(), "Scan URL") + defer span.End() opts := parseScanOptions(form.Opts) opts.Header = ctx.Request.Header @@ -42,21 +38,18 @@ func (h *Handler) ScanURL(ctx *gin.Context) { ExcludeScans: form.Opts.ExcludeScans, }) if err != nil { - analyticsx.TrackError(ctx, serverApiUrlTracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(traceCtx, nil) if err != nil { - analyticsx.TrackError(ctx, serverApiUrlTracer, err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - if reporter.HasIssue() { - analyticsx.TrackEvent(ctx, serverApiUrlTracer, "Issue Found", nil) - } + analytics.TrackScanReport(traceCtx, reporter) ctx.JSON(http.StatusOK, HTTPResponseReports{ Reports: reporter.GetScanReports(), diff --git a/api/graphql.go b/api/graphql.go index dc48702d..9da08299 100644 --- a/api/graphql.go +++ b/api/graphql.go @@ -3,13 +3,12 @@ package api import ( "net/http" + "github.com/cerberauth/vulnapi/internal/analytics" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/gin-gonic/gin" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) type NewGraphQLScanRequest struct { @@ -18,8 +17,6 @@ type NewGraphQLScanRequest struct { Opts *ScanOptions `json:"options"` } -var serverApiGraphQLTracer = otel.Tracer("server/api/graphql") - func (h *Handler) ScanGraphQL(ctx *gin.Context) { var form NewGraphQLScanRequest if err := ctx.ShouldBindJSON(&form); err != nil { @@ -27,7 +24,9 @@ func (h *Handler) ScanGraphQL(ctx *gin.Context) { return } - analyticsx.TrackEvent(ctx, serverApiGraphQLTracer, "Scan GraphQL", []attribute.KeyValue{}) + traceCtx, span := tracer.Start(ctx, "Scan GraphQL") + defer span.End() + opts := parseScanOptions(form.Opts) opts.Header = ctx.Request.Header opts.Cookies = ctx.Request.Cookies() @@ -38,21 +37,20 @@ func (h *Handler) ScanGraphQL(ctx *gin.Context) { ExcludeScans: form.Opts.ExcludeScans, }) if err != nil { - analyticsx.TrackError(ctx, serverApiGraphQLTracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(traceCtx, nil) if err != nil { - analyticsx.TrackError(ctx, serverApiGraphQLTracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - if reporter.HasIssue() { - analyticsx.TrackEvent(ctx, serverApiGraphQLTracer, "Issue Found", nil) - } + analytics.TrackScanReport(traceCtx, reporter) ctx.JSON(http.StatusOK, HTTPResponseReports{ Reports: reporter.GetScanReports(), diff --git a/api/handler.go b/api/handler.go index 45066f76..1a38c210 100644 --- a/api/handler.go +++ b/api/handler.go @@ -1,6 +1,11 @@ package api -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + "go.opentelemetry.io/otel" +) + +var tracer = otel.Tracer("server/api") type Handler struct{} diff --git a/api/openapi.go b/api/openapi.go index 027c5c91..a23e9232 100644 --- a/api/openapi.go +++ b/api/openapi.go @@ -4,15 +4,14 @@ import ( "encoding/json" "net/http" + "github.com/cerberauth/vulnapi/internal/analytics" "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/openapi" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/gin-gonic/gin" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) type NewOpenAPIScanRequest struct { @@ -24,8 +23,6 @@ type NewOpenAPIScanRequest struct { Opts *ScanOptions `json:"options"` } -var serverApiOpenAPITracer = otel.Tracer("server/api/openapi") - func (h *Handler) ScanOpenAPI(ctx *gin.Context) { var form NewOpenAPIScanRequest if err := ctx.ShouldBindJSON(&form); err != nil { @@ -33,20 +30,24 @@ func (h *Handler) ScanOpenAPI(ctx *gin.Context) { return } - openapi, err := openapi.LoadFromData(ctx, []byte(form.Schema)) + traceCtx, span := tracer.Start(ctx, "Scan OpenAPI") + defer span.End() + + openapi, err := openapi.LoadFromData(traceCtx, []byte(form.Schema)) if err != nil { - analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := openapi.Validate(ctx); err != nil { - analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - analyticsx.TrackEvent(ctx, serverApiOpenAPITracer, "Scan OpenAPI", []attribute.KeyValue{}) opts := parseScanOptions(form.Opts) opts.Header = ctx.Request.Header opts.Cookies = ctx.Request.Cookies() @@ -64,28 +65,28 @@ func (h *Handler) ScanOpenAPI(ctx *gin.Context) { ExcludeScans: form.Opts.ExcludeScans, }) if err != nil { - analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(traceCtx, nil) if err != nil { - analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - if reporter.HasIssue() { - analyticsx.TrackEvent(ctx, serverApiOpenAPITracer, "Issue Found", nil) - } + analytics.TrackScanReport(traceCtx, reporter) response := HTTPResponseReports{ Reports: reporter.GetScanReports(), } _, err = json.Marshal(response) if err != nil { - analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } diff --git a/cmd/discover/api.go b/cmd/discover/api.go index 62e78a6b..cf7ff6bb 100644 --- a/cmd/discover/api.go +++ b/cmd/discover/api.go @@ -4,15 +4,14 @@ import ( "log" "net/http" + "github.com/cerberauth/vulnapi/internal/analytics" internalCmd "github.com/cerberauth/vulnapi/internal/cmd" "github.com/cerberauth/vulnapi/internal/cmd/printtable" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) func NewAPICmd() (apiCmd *cobra.Command) { @@ -21,14 +20,15 @@ func NewAPICmd() (apiCmd *cobra.Command) { Short: "Discover api endpoints and server information", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("discover") baseUrl := args[0] - analyticsx.TrackEvent(ctx, tracer, "Discover API", []attribute.KeyValue{}) + ctx, span := tracer.Start(cmd.Context(), "Discover API") + defer span.End() + client, err := internalCmd.NewHTTPClientFromArgs(internalCmd.GetRateLimit(), internalCmd.GetProxy(), internalCmd.GetHeaders(), internalCmd.GetCookies()) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -37,7 +37,8 @@ func NewAPICmd() (apiCmd *cobra.Command) { ExcludeScans: internalCmd.GetExcludeScans(), }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -47,18 +48,19 @@ func NewAPICmd() (apiCmd *cobra.Command) { // nolint:errcheck defer bar.Finish() } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { + reporter, _, err := s.Execute(ctx, func(operationScan *scan.OperationScan) { if bar != nil { // nolint:errcheck bar.Add(1) } }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } - internalCmd.TrackScanReport(ctx, tracer, reporter) + analytics.TrackScanReport(ctx, reporter) printtable.WellKnownPathsScanReport(reporter) printtable.FingerprintScanReport(reporter) }, diff --git a/cmd/discover/domain.go b/cmd/discover/domain.go index 03b0b9db..0dabc678 100644 --- a/cmd/discover/domain.go +++ b/cmd/discover/domain.go @@ -4,15 +4,14 @@ import ( "fmt" "log" + "github.com/cerberauth/vulnapi/internal/analytics" internalCmd "github.com/cerberauth/vulnapi/internal/cmd" "github.com/cerberauth/vulnapi/internal/cmd/printtable" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) func NewDomainCmd() (domainCmd *cobra.Command) { @@ -21,14 +20,15 @@ func NewDomainCmd() (domainCmd *cobra.Command) { Short: "Discover subdomains with API endpoints", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("discover") domain := args[0] - analyticsx.TrackEvent(ctx, tracer, "Discover Domain", []attribute.KeyValue{}) + ctx, span := tracer.Start(cmd.Context(), "Discover Domain") + defer span.End() + client, err := internalCmd.NewHTTPClientFromArgs(internalCmd.GetRateLimit(), internalCmd.GetProxy(), internalCmd.GetHeaders(), internalCmd.GetCookies()) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -38,7 +38,8 @@ func NewDomainCmd() (domainCmd *cobra.Command) { ExcludeScans: internalCmd.GetExcludeScans(), }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } fmt.Printf("Found %d Domains\n", len(scans)) @@ -53,18 +54,19 @@ func NewDomainCmd() (domainCmd *cobra.Command) { // nolint:errcheck defer bar.Finish() } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { + reporter, _, err := s.Execute(ctx, func(operationScan *scan.OperationScan) { if bar != nil { // nolint:errcheck bar.Add(1) } }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } - internalCmd.TrackScanReport(ctx, tracer, reporter) + analytics.TrackScanReport(ctx, reporter) printtable.WellKnownPathsScanReport(reporter) printtable.FingerprintScanReport(reporter) } diff --git a/cmd/discover/root.go b/cmd/discover/root.go index 98325e03..bfda534d 100644 --- a/cmd/discover/root.go +++ b/cmd/discover/root.go @@ -2,8 +2,11 @@ package discover import ( "github.com/spf13/cobra" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("cmd/discover") + func NewDiscoverCmd() (discoverCmd *cobra.Command) { discoverCmd = &cobra.Command{ Use: "discover [type]", diff --git a/cmd/jwt/root.go b/cmd/jwt/root.go index a9208ef7..f6ff7354 100644 --- a/cmd/jwt/root.go +++ b/cmd/jwt/root.go @@ -7,13 +7,16 @@ import ( "strings" "github.com/cerberauth/vulnapi/jwt" - "github.com/cerberauth/x/analyticsx" jwtlib "github.com/golang-jwt/jwt/v5" "github.com/spf13/cobra" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) +var tracer = otel.Tracer("cmd/jwt") + type Algorithm string const ( @@ -70,15 +73,17 @@ func NewJWTCmd() (cmd *cobra.Command) { Short: "Generate a new JWT token from an existing token", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("jwt") + var span trace.Span + _, span = tracer.Start(cmd.Context(), "Generate JWT Command") + defer span.End() tokenString := args[0] var key interface{} var newTokenString string tokenWriter, err := jwt.NewJWTWriter(tokenString) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) return } @@ -90,7 +95,8 @@ func NewJWTCmd() (cmd *cobra.Command) { var signingMethod jwtlib.SigningMethod if alg != "" { if signingMethod, err = GetAlgorithm(alg); err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) return } @@ -102,17 +108,19 @@ func NewJWTCmd() (cmd *cobra.Command) { if signingMethod == nil || key == nil { err = errors.New("algorithm and secret are required") - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) return } - analyticsx.TrackEvent(ctx, tracer, "Generate JWT", []attribute.KeyValue{ + span.SetAttributes( attribute.String("alg", signingMethod.Alg()), - }) + ) newTokenString, err = tokenWriter.SignWithMethodAndKey(signingMethod, key) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) return } diff --git a/cmd/root.go b/cmd/root.go index 9ad0a8c3..d4cee825 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,8 +30,7 @@ func NewRootCmd(projectVersion string) (cmd *cobra.Command) { Short: "vulnapi", PersistentPreRun: func(cmd *cobra.Command, args []string) { if !sqaOptOut { - ctx := cmd.Context() - _, err := analytics.NewAnalytics(ctx, projectVersion) + _, err := analytics.NewAnalytics(cmd.Context(), projectVersion) if err != nil { fmt.Println("Failed to initialize analytics:", err) } diff --git a/cmd/scan/curl.go b/cmd/scan/curl.go index 69a55ef6..0b13a2fc 100644 --- a/cmd/scan/curl.go +++ b/cmd/scan/curl.go @@ -3,15 +3,14 @@ package scan import ( "log" + "github.com/cerberauth/vulnapi/internal/analytics" internalCmd "github.com/cerberauth/vulnapi/internal/cmd" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) func NewCURLScanCmd() (scanCmd *cobra.Command) { @@ -29,16 +28,15 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { UnknownFlags: true, }, Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("scan/curl") curlUrl = args[0] - analyticsx.TrackEvent(ctx, tracer, "Scan CURL", []attribute.KeyValue{ - attribute.String("method", curlMethod), - }) + ctx, span := tracer.Start(cmd.Context(), "Scan cURL") + defer span.End() + client, err := internalCmd.NewHTTPClientFromArgs(internalCmd.GetRateLimit(), internalCmd.GetProxy(), internalCmd.GetHeaders(), internalCmd.GetCookies()) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } request.SetDefaultClient(client) @@ -48,7 +46,8 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { ExcludeScans: internalCmd.GetExcludeScans(), }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -58,21 +57,23 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { // nolint:errcheck defer bar.Finish() } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { + reporter, _, err := s.Execute(ctx, func(operationScan *scan.OperationScan) { if bar != nil { // nolint:errcheck bar.Add(1) } }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } - internalCmd.TrackScanReport(ctx, tracer, reporter) + analytics.TrackScanReport(ctx, reporter) err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } }, diff --git a/cmd/scan/graphql.go b/cmd/scan/graphql.go index fe10cd26..9a684444 100644 --- a/cmd/scan/graphql.go +++ b/cmd/scan/graphql.go @@ -3,15 +3,14 @@ package scan import ( "log" + "github.com/cerberauth/vulnapi/internal/analytics" internalCmd "github.com/cerberauth/vulnapi/internal/cmd" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) func NewGraphQLScanCmd() (scanCmd *cobra.Command) { @@ -23,14 +22,15 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { UnknownFlags: true, }, Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("scan/graphql") graphqlEndpoint := args[0] - analyticsx.TrackEvent(ctx, tracer, "Scan GraphQL", []attribute.KeyValue{}) + ctx, span := tracer.Start(cmd.Context(), "Scan GraphQL") + defer span.End() + client, err := internalCmd.NewHTTPClientFromArgs(internalCmd.GetRateLimit(), internalCmd.GetProxy(), internalCmd.GetHeaders(), internalCmd.GetCookies()) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } request.SetDefaultClient(client) @@ -40,7 +40,8 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { ExcludeScans: internalCmd.GetExcludeScans(), }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -50,21 +51,23 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { // nolint:errcheck defer bar.Finish() } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { + reporter, _, err := s.Execute(ctx, func(operationScan *scan.OperationScan) { if bar != nil { // nolint:errcheck bar.Add(1) } }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } - internalCmd.TrackScanReport(ctx, tracer, reporter) + analytics.TrackScanReport(ctx, reporter) err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } }, diff --git a/cmd/scan/openapi.go b/cmd/scan/openapi.go index ac5c4d53..8c78fdd3 100644 --- a/cmd/scan/openapi.go +++ b/cmd/scan/openapi.go @@ -5,17 +5,16 @@ import ( "log" "os" + "github.com/cerberauth/vulnapi/internal/analytics" "github.com/cerberauth/vulnapi/internal/auth" internalCmd "github.com/cerberauth/vulnapi/internal/cmd" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/openapi" "github.com/cerberauth/vulnapi/scan" "github.com/cerberauth/vulnapi/scenario" - "github.com/cerberauth/x/analyticsx" "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) func isStdinOpen() bool { @@ -43,18 +42,21 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { Short: "OpenAPI Operations Scan", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - tracer := otel.Tracer("scan/openapi") openapiUrlOrPath := args[0] + ctx, span := tracer.Start(cmd.Context(), "Scan OpenAPI") + defer span.End() + openapi, err := openapi.LoadOpenAPI(ctx, openapiUrlOrPath) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } if err := openapi.Validate(ctx); err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -69,10 +71,10 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { } securitySchemesValues := auth.NewSecuritySchemeValues(values).WithDefault(validToken) - analyticsx.TrackEvent(ctx, tracer, "Scan OpenAPI", []attribute.KeyValue{}) client, err := internalCmd.NewHTTPClientFromArgs(internalCmd.GetRateLimit(), internalCmd.GetProxy(), internalCmd.GetHeaders(), internalCmd.GetCookies()) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } request.SetDefaultClient(client) @@ -82,7 +84,8 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { ExcludeScans: internalCmd.GetExcludeScans(), }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } @@ -92,20 +95,22 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { // nolint:errcheck defer bar.Finish() } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { + reporter, _, err := s.Execute(ctx, func(operationScan *scan.OperationScan) { if bar != nil { // nolint:errcheck bar.Add(1) } }) if err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } - internalCmd.TrackScanReport(ctx, tracer, reporter) + analytics.TrackScanReport(ctx, reporter) if err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter); err != nil { - analyticsx.TrackError(ctx, tracer, err) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Fatal(err) } }, diff --git a/cmd/scan/root.go b/cmd/scan/root.go index a1468f46..a152d82b 100644 --- a/cmd/scan/root.go +++ b/cmd/scan/root.go @@ -2,8 +2,11 @@ package scan import ( "github.com/spf13/cobra" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("cmd/scan") + func NewScanCmd() (scanCmd *cobra.Command) { scanCmd = &cobra.Command{ Use: "scan [type]", diff --git a/go.mod b/go.mod index b4041457..d9e40e40 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( github.com/brianvoe/gofakeit/v7 v7.0.4 - github.com/cerberauth/x v0.0.0-20240929131055-0a38dd31aeda + github.com/cerberauth/x v0.0.0-20241109114817-0b7a59bd5ced github.com/getkin/kin-openapi v0.128.0 github.com/gin-contrib/requestid v1.0.3 github.com/gin-gonic/gin v1.10.0 @@ -18,12 +18,11 @@ require ( github.com/std-uritemplate/std-uritemplate/go v1.0.6 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.55.0 - go.opentelemetry.io/otel v1.31.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 - go.opentelemetry.io/otel/sdk v1.31.0 - go.opentelemetry.io/otel/trace v1.31.0 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 go.uber.org/ratelimit v0.3.1 - golang.org/x/text v0.19.0 + golang.org/x/text v0.20.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -46,7 +45,7 @@ require ( github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -67,16 +66,19 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/arch v0.10.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index 21ea3a9d..98b9ece1 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cerberauth/x v0.0.0-20240929131055-0a38dd31aeda h1:D2Zg/XW7L/ErDZjA9sj+GMaKdvaadTaYKiqv4iUO8Do= -github.com/cerberauth/x v0.0.0-20240929131055-0a38dd31aeda/go.mod h1:l3nkeJfebGk9Rn2Uaz/xhKMkfHHp3K2ILl4f5WI7/uk= +github.com/cerberauth/x v0.0.0-20241109114817-0b7a59bd5ced h1:TXwL3iYBYRWrdXkgEzAf40bIWh/3UuhNRF00pmTTtcs= +github.com/cerberauth/x v0.0.0-20241109114817-0b7a59bd5ced/go.mod h1:FNXUEJmpyI56sqOCuGg2SSKq5FxwnRfqQ/wxmIkx8Zw= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= @@ -59,8 +59,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -143,18 +143,22 @@ go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0. go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.55.0/go.mod h1:8aCCTMjP225r98yevEMM5NYDb3ianWLoeIzZ1rPyxHU= go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= @@ -169,16 +173,16 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index 110ecb91..727d2542 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -2,10 +2,8 @@ package analytics import ( "context" - "time" - "github.com/cerberauth/x/analyticsx" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "github.com/cerberauth/x/otelx" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) @@ -13,13 +11,11 @@ var tracerProvider *sdktrace.TracerProvider func NewAnalytics(ctx context.Context, projectVersion string) (*sdktrace.TracerProvider, error) { var err error - tracerProvider, err = analyticsx.NewAnalytics(ctx, analyticsx.AppInfo{ - Name: "vulnapi", - Version: projectVersion, - }, otlptracehttp.WithTimeout(time.Second*2), otlptracehttp.WithRetry(otlptracehttp.RetryConfig{Enabled: false})) + tracerProvider, err = otelx.InitTracerProvider(ctx, otelx.InitResource("vulnapi", projectVersion)) if err != nil { return nil, err } + return tracerProvider, err } diff --git a/internal/analytics/scan_report.go b/internal/analytics/scan_report.go new file mode 100644 index 00000000..a6016893 --- /dev/null +++ b/internal/analytics/scan_report.go @@ -0,0 +1,17 @@ +package analytics + +import ( + "context" + + "github.com/cerberauth/vulnapi/report" +) + +func TrackScanReport(ctx context.Context, reporter *report.Reporter) { + failedIssueReports := reporter.GetFailedIssueReports() + var higherSeverityCVSS float64 = 0 + for _, failedIssueReport := range failedIssueReports { + if failedIssueReport.CVSS.Score > higherSeverityCVSS { + higherSeverityCVSS = failedIssueReport.CVSS.Score + } + } +} diff --git a/internal/auth/oauth.go b/internal/auth/oauth.go index 3e3b4a4d..90798add 100644 --- a/internal/auth/oauth.go +++ b/internal/auth/oauth.go @@ -44,7 +44,7 @@ func NewOAuthSecurityScheme(name string, value *string, cfg *OAuthConfig) *OAuth } return &OAuthSecurityScheme{ - Type: HttpType, + Type: OAuth2, Scheme: BearerScheme, In: InHeader, Name: name, diff --git a/internal/auth/oauth_test.go b/internal/auth/oauth_test.go index 514939ca..851dceec 100644 --- a/internal/auth/oauth_test.go +++ b/internal/auth/oauth_test.go @@ -15,7 +15,7 @@ func TestNewOAuthSecurityScheme(t *testing.T) { ss := auth.NewOAuthSecurityScheme(name, &value, nil) - assert.Equal(t, auth.HttpType, ss.Type) + assert.Equal(t, auth.OAuth2, ss.Type) assert.Equal(t, auth.BearerScheme, ss.Scheme) assert.Equal(t, auth.InHeader, ss.In) assert.Equal(t, name, ss.Name) @@ -30,9 +30,6 @@ func TestNewOAuthSecurityScheme_WithJWT(t *testing.T) { ss := auth.NewOAuthSecurityScheme(name, &value, nil) - assert.Equal(t, auth.HttpType, ss.Type) - assert.Equal(t, auth.BearerScheme, ss.Scheme) - assert.Equal(t, auth.InHeader, ss.In) assert.Equal(t, name, ss.Name) assert.Equal(t, &value, ss.ValidValue) assert.Equal(t, "", ss.AttackValue) @@ -56,7 +53,7 @@ func TestOAuthSecurityScheme_GetType(t *testing.T) { scheme := ss.GetType() - assert.Equal(t, auth.HttpType, scheme) + assert.Equal(t, auth.OAuth2, scheme) } func TestOAuthSecurityScheme_GetIn(t *testing.T) { diff --git a/internal/auth/scheme.go b/internal/auth/scheme.go index 480f64d1..1e6222e0 100644 --- a/internal/auth/scheme.go +++ b/internal/auth/scheme.go @@ -1,42 +1,22 @@ package auth -import ( - "context" - "errors" - - "github.com/cerberauth/x/analyticsx" - "go.opentelemetry.io/otel" -) - type SchemeName string // Values are registred in the IANA Authentication Scheme registry // https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml const ( - BasicScheme SchemeName = "basic" - BearerScheme SchemeName = "bearer" - DigestScheme SchemeName = "digest" - OAuthScheme SchemeName = "oauth" - PrivateToken SchemeName = "privateToken" - NoneScheme SchemeName = "none" + BasicScheme SchemeName = "Basic" + BearerScheme SchemeName = "Bearer" + DigestScheme SchemeName = "Digest" + OAuthScheme SchemeName = "OAuth" + PrivateToken SchemeName = "PrivateToken" + NoneScheme SchemeName = "None" ) func (s *SchemeName) String() string { return string(*s) } -func (s *SchemeName) Set(v string) error { - switch v { - case "basic", "bearer", "digest", "oauth", "privateToken": - *s = SchemeName(v) - return nil - default: - err := errors.New(`must be one of "basic", "bearer", "digest", "oauth", "privateToken"`) - analyticsx.TrackError(context.TODO(), otel.Tracer("jwt"), err) - return err - } -} - func (e *SchemeName) Type() string { return "scheme" } diff --git a/internal/auth/scheme_test.go b/internal/auth/scheme_test.go index 7229cc80..501125f5 100644 --- a/internal/auth/scheme_test.go +++ b/internal/auth/scheme_test.go @@ -9,22 +9,7 @@ import ( func TestSchemeName_String(t *testing.T) { scheme := auth.BasicScheme - assert.Equal(t, "basic", scheme.String()) -} - -func TestSchemeName_Set_Valid(t *testing.T) { - scheme := auth.SchemeName("") - err := scheme.Set("bearer") - assert.NoError(t, err) - assert.Equal(t, auth.BearerScheme, scheme) -} - -func TestSchemeName_Set_Invalid(t *testing.T) { - scheme := auth.SchemeName("") - err := scheme.Set("invalid") - assert.Error(t, err) - assert.EqualError(t, err, `must be one of "basic", "bearer", "digest", "oauth", "privateToken"`) - assert.Equal(t, auth.SchemeName(""), scheme) + assert.Equal(t, "Basic", scheme.String()) } func TestSchemeName_Type(t *testing.T) { diff --git a/internal/auth/security_scheme.go b/internal/auth/security_scheme.go index b3a602de..5d752232 100644 --- a/internal/auth/security_scheme.go +++ b/internal/auth/security_scheme.go @@ -19,3 +19,16 @@ type SecurityScheme interface { GetAttackValue() interface{} } type SecuritySchemesMap map[string]SecurityScheme + +func GetSecuritySchemeUniqueName(securityScheme SecurityScheme) string { + if securityScheme == nil { + return "" + } + + uniqueName := string(securityScheme.GetType()) + "-" + string(securityScheme.GetScheme()) + if securityScheme.GetIn() != nil { + uniqueName += "-" + string(*securityScheme.GetIn()) + } + + return uniqueName +} diff --git a/internal/auth/security_scheme_test.go b/internal/auth/security_scheme_test.go new file mode 100644 index 00000000..0861e660 --- /dev/null +++ b/internal/auth/security_scheme_test.go @@ -0,0 +1,50 @@ +package auth_test + +import ( + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/stretchr/testify/assert" +) + +func TestGetSecuritySchemeUniqueName(t *testing.T) { + noAuthSecurityScheme := auth.NewNoAuthSecurityScheme() + bearerSecurityScheme := auth.NewAuthorizationBearerSecurityScheme("name", nil) + jwtBearerSecurityScheme, _ := auth.NewAuthorizationJWTBearerSecurityScheme("name", nil) + oauthSecurityScheme := auth.NewOAuthSecurityScheme("name", nil, nil) + + tests := []struct { + name string + securityScheme auth.SecurityScheme + expected string + }{ + { + name: "no auth security scheme", + securityScheme: noAuthSecurityScheme, + expected: "none-None", + }, + { + name: "bearer security scheme", + securityScheme: bearerSecurityScheme, + expected: "http-Bearer-header", + }, + { + name: "jwt bearer security scheme", + securityScheme: jwtBearerSecurityScheme, + expected: "http-Bearer-header", + }, + { + name: "oauth security scheme", + securityScheme: oauthSecurityScheme, + expected: "oauth2-Bearer-header", + }, + } + + assert.Equal(t, "", auth.GetSecuritySchemeUniqueName(nil)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := auth.GetSecuritySchemeUniqueName(tt.securityScheme) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/cmd/analytics.go b/internal/cmd/analytics.go deleted file mode 100644 index d2497222..00000000 --- a/internal/cmd/analytics.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "context" - - "github.com/cerberauth/vulnapi/report" - "github.com/cerberauth/x/analyticsx" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -func TrackScanReport(ctx context.Context, tracer trace.Tracer, reporter *report.Reporter) { - analyticsx.TrackEvent(ctx, tracer, "Scan Report", []attribute.KeyValue{ - attribute.Int("issuesCount", len(reporter.GetIssueReports())), - attribute.Bool("hasIssue", reporter.HasIssue()), - attribute.Bool("hasHighRiskSeverityIssue", reporter.HasHighRiskOrHigherSeverityIssue()), - }) -} diff --git a/report/issue_report.go b/report/issue_report.go index 27e5522e..1499c126 100644 --- a/report/issue_report.go +++ b/report/issue_report.go @@ -1,10 +1,13 @@ package report import ( + "context" "fmt" "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/operation" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) type IssueReportStatus string @@ -65,6 +68,14 @@ func (vr *IssueReport) WithBooleanStatus(status bool) *IssueReport { } func (vr *IssueReport) Fail() *IssueReport { + _, span := tracer.Start(context.Background(), "Issue.Failed", trace.WithAttributes( + attribute.String("id", vr.Issue.ID), + attribute.String("name", vr.Issue.Name), + attribute.Float64("CVSS", vr.Issue.CVSS.Score), + attribute.String("securityScheme", auth.GetSecuritySchemeUniqueName(vr.SecurityScheme)), + )) + span.End() + vr.Status = IssueReportStatusFailed return vr } diff --git a/report/report.go b/report/report.go index 08b26203..871f7324 100644 --- a/report/report.go +++ b/report/report.go @@ -7,6 +7,7 @@ import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/operation" "github.com/cerberauth/vulnapi/internal/scan" + "go.opentelemetry.io/otel" ) type OperationSecurityScheme struct { @@ -49,6 +50,7 @@ type ScanReportScan struct { type ScanReportOperation struct { ID string `json:"id" yaml:"id"` } + type ScanReport struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` @@ -62,6 +64,8 @@ type ScanReport struct { Issues []*IssueReport `json:"issues" yaml:"issues"` } +var tracer = otel.Tracer("report") + func NewScanReport(id string, name string, operation *operation.Operation) *ScanReport { var scanOperation *ScanReportOperation if operation != nil && operation.ID != "" { diff --git a/scan/scan.go b/scan/scan.go index ca07dead..3dc80312 100644 --- a/scan/scan.go +++ b/scan/scan.go @@ -1,11 +1,15 @@ package scan import ( + "context" "fmt" "regexp" + "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/operation" "github.com/cerberauth/vulnapi/report" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" ) type ScanOptions struct { @@ -21,6 +25,8 @@ type Scan struct { OperationsScans []OperationScan } +var tracer = otel.Tracer("scan") + func NewScan(operations operation.Operations, opts *ScanOptions) (*Scan, error) { if len(operations) == 0 { return nil, fmt.Errorf("a scan must have at least one operation") @@ -72,7 +78,10 @@ func (s *Scan) AddScanHandler(handler *OperationScanHandler) *Scan { return s } -func (s *Scan) Execute(scanCallback func(operationScan *OperationScan)) (*report.Reporter, []error, error) { +func (s *Scan) Execute(ctx context.Context, scanCallback func(operationScan *OperationScan)) (*report.Reporter, []error, error) { + ctx, span := tracer.Start(ctx, "Execute Scan") + defer span.End() + if scanCallback == nil { scanCallback = func(operationScan *OperationScan) {} } @@ -83,8 +92,23 @@ func (s *Scan) Execute(scanCallback func(operationScan *OperationScan)) (*report continue } - report, err := scan.ScanHandler.Handler(scan.Operation, scan.Operation.SecuritySchemes[0]) // TODO: handle multiple security schemes + operationCtx, operationSpan := tracer.Start(ctx, "Operation Scan") + operationSpan.SetAttributes( + attribute.String("method", scan.Operation.Method), + attribute.String("handler", scan.ScanHandler.ID), + ) + + securityScheme := scan.Operation.SecuritySchemes[0] // TODO: handle multiple security schemes + _, operationSecuritySchemeSpan := tracer.Start(operationCtx, "Using Security Scheme") + operationSecuritySchemeSpan.SetAttributes( + attribute.String("name", auth.GetSecuritySchemeUniqueName(securityScheme)), + attribute.String("type", string(securityScheme.GetType())), + attribute.String("scheme", string(securityScheme.GetScheme())), + ) + + report, err := scan.ScanHandler.Handler(scan.Operation, securityScheme) if err != nil { + operationSpan.RecordError(err) errors = append(errors, err) } @@ -93,6 +117,8 @@ func (s *Scan) Execute(scanCallback func(operationScan *OperationScan)) (*report } scanCallback(&scan) + operationSecuritySchemeSpan.End() + operationSpan.End() } return s.Reporter, errors, nil diff --git a/scan/scan_test.go b/scan/scan_test.go index 031e0686..219915dd 100644 --- a/scan/scan_test.go +++ b/scan/scan_test.go @@ -1,6 +1,7 @@ package scan_test import ( + "context" "net/http" "testing" @@ -87,7 +88,7 @@ func TestScanExecuteWithNoHandlers(t *testing.T) { operations := operation.Operations{op} s, _ := scan.NewScan(operations, nil) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -103,7 +104,7 @@ func TestScanExecuteWithHandler(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -122,7 +123,7 @@ func TestScanExecuteWithIncludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -141,7 +142,7 @@ func TestScanExecuteWithEmptyStringIncludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -160,7 +161,7 @@ func TestScanExecuteWithMatchStringIncludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -179,7 +180,7 @@ func TestScanExecuteWithWrongMatchStringIncludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -197,7 +198,7 @@ func TestScanExecuteWithExcludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -215,7 +216,7 @@ func TestScanExecuteWithMatchStringExcludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors) @@ -233,7 +234,7 @@ func TestScanExecuteWithWrongMatchStringExcludeScans(t *testing.T) { }) s.AddOperationScanHandler(handler) - reporter, errors, err := s.Execute(nil) + reporter, errors, err := s.Execute(context.TODO(), nil) require.NoError(t, err) assert.Empty(t, errors)