diff --git a/error.go b/error.go index 0f71eb0..8289d89 100644 --- a/error.go +++ b/error.go @@ -10,12 +10,14 @@ import ( "github.com/rs/zerolog/log" "go.mondoo.com/ranger-rpc/codes" "go.mondoo.com/ranger-rpc/status" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/protobuf/proto" ) // HttpError writes an error to the response. -func HttpError(w http.ResponseWriter, req *http.Request, err error) { +func HttpError(span trace.Span, w http.ResponseWriter, req *http.Request, err error) { // check if the accept header is set, otherwise use the incoming content type accept := determineResponseType(req.Header.Get("Content-Type"), req.Header.Get("Accept")) @@ -29,6 +31,10 @@ func HttpError(w http.ResponseWriter, req *http.Request, err error) { // write status code status := status.HTTPStatusFromCode(s.Code()) + span.RecordError(err, trace.WithAttributes( + attribute.Int("mondoo.ranger.status", status), + )) + if status >= 500 { log.Error(). Err(err). diff --git a/plugins/rangerguard/mux.go b/plugins/rangerguard/mux.go index 58e5e58..a0dd707 100644 --- a/plugins/rangerguard/mux.go +++ b/plugins/rangerguard/mux.go @@ -78,6 +78,9 @@ func (r *guardMux) Use(mwf ...http.HandlerFunc) { func (gm guardMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { guardCtx, span := tracer.Start(r.Context(), "ranger.guard.ServeHTTP") defer span.End() + + r = r.WithContext(guardCtx) + // iterate over middle ware for i := len(gm.middlewares) - 1; i >= 0; i-- { fn := gm.middlewares[i] @@ -88,20 +91,17 @@ func (gm guardMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, authenticated, authnReq := gm.authenticate(r) if !authenticated || user == nil { log.Info().Str("component", "guard").Str("client", r.RemoteAddr).Str("uri", r.RequestURI).Msg("Unauthenticated") - ranger.HttpError(w, authnReq, AUTHENTICATION_DENIED_ERROR) + ranger.HttpError(span, w, authnReq, AUTHENTICATION_DENIED_ERROR) span.End() return } // tell hooks about the user for i := range gm.hooks { - _, span := tracer.Start(guardCtx, "ranger.guard.ServeHTTP/hook/"+gm.hooks[i].Name()) newCtx, err := gm.hooks[i].Run(authnReq.Context(), user, r) - span.End() if err != nil { log.Error().Err(err).Str("hook", gm.hooks[i].Name()).Msg("could not authenticate because ranger guard hook returned an error") - ranger.HttpError(w, authnReq, AUTHENTICATION_DENIED_ERROR) - span.End() + ranger.HttpError(span, w, authnReq, AUTHENTICATION_DENIED_ERROR) return } // assign new context @@ -113,12 +113,10 @@ func (gm guardMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { authorized, authzReq := gm.authorize(authnReq, user) if !authorized { log.Info().Str("component", "guard").Str("client", r.RemoteAddr).Str("uri", r.RequestURI).Msg("Unauthorized") - ranger.HttpError(w, authzReq, PERMISSION_DENIED_ERROR) - span.End() + ranger.HttpError(span, w, authzReq, PERMISSION_DENIED_ERROR) return } - span.End() gm.next.ServeHTTP(w, authzReq) } diff --git a/server.go b/server.go index 2ae58fe..3545c50 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,9 @@ import ( "go.mondoo.com/ranger-rpc/codes" "go.mondoo.com/ranger-rpc/status" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" jsonpb "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -30,6 +33,8 @@ var validContentTypes = map[string]struct{}{ ContentTypeJson: {}, } +var tracer = otel.Tracer("go.mondoo.com/mondoo/ranger-rpc") + // Method represents a RPC method and is used by protoc-gen-rangerrpc type Method func(ctx context.Context, reqBytes *[]byte) (proto.Message, error) @@ -57,36 +62,44 @@ type server struct { // ServeHTTP is the main entry point for the http server. func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) { - ctx, cancel := context.WithCancel(req.Context()) + ctx, span := tracer.Start(req.Context(), "ranger.server.ServeHTTP", trace.WithAttributes( + attribute.String("mondoo.ranger.service", s.service.Name))) + defer span.End() + + ctx, cancel := context.WithCancel(ctx) defer cancel() + req = req.WithContext(ctx) + contentType := req.Header.Get("Content-Type") // verify content type err := verifyContentType(req, contentType) if err != nil { - HttpError(w, req, err) + HttpError(span, w, req, err) return } if !strings.HasPrefix(req.URL.Path, s.prefix) { - HttpError(w, req, status.Error(codes.NotFound, req.URL.Path+" is not available")) + HttpError(span, w, req, status.Error(codes.NotFound, req.URL.Path+" is not available")) return } // extract the rpc method name and invoke the method name := strings.TrimPrefix(req.URL.Path, s.prefix) + span.SetAttributes(attribute.String("mondoo.ranger.method", name)) + method, ok := s.service.Methods[name] if !ok { err := status.Error(codes.NotFound, "method not defined") - HttpError(w, req, err) + HttpError(span, w, req, err) return } rctx, rcancel, body, err := preProcessRequest(ctx, req) if err != nil { - HttpError(w, req, err) + HttpError(span, w, req, err) return } defer rcancel() @@ -94,7 +107,7 @@ func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) { // invoke method and send the response resp, err := method(rctx, &body) if err != nil { - HttpError(w, req, err) + HttpError(span, w, req, err) return } // check if the accept header is set, otherwise use the incoming content type @@ -125,7 +138,8 @@ func preProcessRequest(ctx context.Context, req *http.Request) (context.Context, func (s *server) sendResponse(w http.ResponseWriter, req *http.Request, resp proto.Message, contentType string) { payload, contentType, err := convertProtoToPayload(resp, contentType) if err != nil { - HttpError(w, req, status.Error(codes.Internal, "error encoding response")) + span := trace.SpanFromContext(req.Context()) + HttpError(span, w, req, status.Error(codes.Internal, "error encoding response")) return }