From c2ad32a83ce02ba890480333baebcfb8e55cd6bf Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Tue, 16 Jul 2024 14:38:54 +0100 Subject: [PATCH] Add in Correct Create Auditing (#69) Previously when something was created, we just said exactly that, but we didn't say what, because we had no idea. Modify the lookup on creation so we have access to the response, can extract the metadata, and get the new ID. Obviously this means every API will need to be updated... --- charts/core/Chart.yaml | 4 +- pkg/server/middleware/audit/logging.go | 42 +++++++++++++++---- .../middleware/opentelemetry/opentelemetry.go | 8 +++- pkg/server/middleware/types.go | 17 +++++--- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/charts/core/Chart.yaml b/charts/core/Chart.yaml index 46abd58..b54306a 100644 --- a/charts/core/Chart.yaml +++ b/charts/core/Chart.yaml @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn Core type: application -version: v0.1.61 -appVersion: v0.1.61 +version: v0.1.62 +appVersion: v0.1.62 icon: https://assets.unikorn-cloud.org/images/logos/dark-on-light/icon.svg diff --git a/pkg/server/middleware/audit/logging.go b/pkg/server/middleware/audit/logging.go index 6ff1e15..98c1c98 100644 --- a/pkg/server/middleware/audit/logging.go +++ b/pkg/server/middleware/audit/logging.go @@ -17,6 +17,7 @@ limitations under the License. package audit import ( + "encoding/json" "net/http" "regexp" "strings" @@ -59,24 +60,41 @@ func New(next http.Handler, openapi *openapi.Schema, application, version string } // getResource will resolve to a resource type. -func getResource(route *routers.Route, params map[string]string) *Resource { - // We are looking for "/.../resource/{idParameter}" - // or failing that "/.../resource" - matches := regexp.MustCompile(`/([^/]+)/{([^/}]+)}$`).FindStringSubmatch(route.Path) - if matches == nil { +func getResource(w *middleware.LoggingResponseWriter, r *http.Request, route *routers.Route, params map[string]string) *Resource { + // Creates rely on the response containing the resource ID in the response metadata. + if r.Method == http.MethodPost { + // Nothing written, possibly a bug somewhere? + if w.Body() == nil { + return nil + } + + var metadata struct { + Metadata openapi.ResourceReadMetadata `json:"metadata"` + } + + // Not a canonical API resource, possibly a bug somewhere? + if err := json.Unmarshal(w.Body().Bytes(), &metadata); err != nil { + return nil + } + segments := strings.Split(route.Path, "/") return &Resource{ Type: segments[len(segments)-1], + ID: metadata.Metadata.Id, } } - resource := &Resource{ + // Read, updates and deletes you can get the information from the route. + matches := regexp.MustCompile(`/([^/]+)/{([^/}]+)}$`).FindStringSubmatch(route.Path) + if matches == nil { + return nil + } + + return &Resource{ Type: matches[1], ID: params[matches[2]], } - - return resource } // ServeHTTP implements the http.Handler interface. @@ -112,6 +130,12 @@ func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // If you cannot derive the resource, then discard. + resource := getResource(writer, r, route, params) + if resource == nil { + return + } + logParams := []any{ "component", &Component{ Name: l.application, @@ -124,7 +148,7 @@ func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) { Verb: r.Method, }, "scope", params, - "resource", getResource(route, params), + "resource", resource, "result", &Result{ Status: writer.StatusCode(), }, diff --git a/pkg/server/middleware/opentelemetry/opentelemetry.go b/pkg/server/middleware/opentelemetry/opentelemetry.go index 5b4941b..6eeb8b7 100644 --- a/pkg/server/middleware/opentelemetry/opentelemetry.go +++ b/pkg/server/middleware/opentelemetry/opentelemetry.go @@ -202,10 +202,16 @@ func httpRequestAttributes(r *http.Request) []attribute.KeyValue { } func httpResponseAttributes(w *middleware.LoggingResponseWriter) []attribute.KeyValue { + var bodySize int + + if body := w.Body(); body != nil { + bodySize = body.Len() + } + var attr []attribute.KeyValue attr = append(attr, semconv.HTTPResponseStatusCode(w.StatusCode())) - attr = append(attr, semconv.HTTPResponseBodySize(w.ContentLength())) + attr = append(attr, semconv.HTTPResponseBodySize(bodySize)) attr = append(attr, httpHeaderAttributes(w.Header(), "http.response.header")...) return attr diff --git a/pkg/server/middleware/types.go b/pkg/server/middleware/types.go index 7dfeffe..9ee849b 100644 --- a/pkg/server/middleware/types.go +++ b/pkg/server/middleware/types.go @@ -17,15 +17,16 @@ limitations under the License. package middleware import ( + "bytes" "net/http" ) // LoggingResponseWriter is the ubiquitous reimplementation of a response // writer that allows access to the HTTP status code in middleware. type LoggingResponseWriter struct { - next http.ResponseWriter - code int - contentLength int + next http.ResponseWriter + code int + body *bytes.Buffer } func NewLoggingResponseWriter(next http.ResponseWriter) *LoggingResponseWriter { @@ -42,7 +43,11 @@ func (w *LoggingResponseWriter) Header() http.Header { } func (w *LoggingResponseWriter) Write(body []byte) (int, error) { - w.contentLength += len(body) + if w.body == nil { + w.body = &bytes.Buffer{} + } + + w.body.Write(body) return w.next.Write(body) } @@ -60,6 +65,6 @@ func (w *LoggingResponseWriter) StatusCode() int { return w.code } -func (w *LoggingResponseWriter) ContentLength() int { - return w.contentLength +func (w *LoggingResponseWriter) Body() *bytes.Buffer { + return w.body }