Skip to content

Commit

Permalink
Add middleware for audit log ext
Browse files Browse the repository at this point in the history
Signed-off-by: stonezdj <[email protected]>
  • Loading branch information
stonezdj committed Jan 3, 2025
1 parent a14a4d2 commit 67d065e
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 2 deletions.
75 changes: 75 additions & 0 deletions src/controller/event/metadata/commonevent/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package commonevent

import (
"context"
"regexp"
"sync"

"github.com/goharbor/harbor/src/pkg/notifier/event"
)

// Resolver the interface to resolve Metadata to CommonEvent
type Resolver interface {
Resolve(*Metadata, *event.Event) error
PreCheck(ctx context.Context, url string, method string) (bool, string)
}

var urlResolvers = map[string]Resolver{}

var mu = &sync.Mutex{}

// RegisterResolver register a resolver for a specific URL pattern
func RegisterResolver(urlPattern string, resolver Resolver) {
mu.Lock()
urlResolvers[urlPattern] = resolver
mu.Unlock()
}

// Resolvers get map of resolvers
func Resolvers() map[string]Resolver {
return urlResolvers
}

// Metadata the raw data of event
type Metadata struct {
// Ctx ...
Ctx context.Context
// Username requester username
Username string
// RequestPayload http request payload
RequestPayload string
// RequestMethod
RequestMethod string
// ResponseCode response code
ResponseCode int
// RequestURL request URL
RequestURL string
// IPAddress IP address of the request
IPAddress string
// ResponseLocation response location
ResponseLocation string
// ResourceName
ResourceName string
}

// Resolve parse the audit information from CommonEventMetadata
func (c *Metadata) Resolve(event *event.Event) error {
for url, r := range Resolvers() {
p := regexp.MustCompile(url)
if p.MatchString(c.RequestURL) {
return r.Resolve(c, event)
}
}
return nil
}

// PreCheck check if current event is matched and return the prefetched resource name when it is delete operation
func (c *Metadata) PreCheck() (bool, string) {
for urlPattern, r := range Resolvers() {
p := regexp.MustCompile(urlPattern)
if p.MatchString(c.RequestURL) {
return r.PreCheck(c.Ctx, c.RequestURL, c.RequestMethod)
}
}
return false, ""
}
2 changes: 1 addition & 1 deletion src/core/middlewares/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ func MiddleWares() []web.MiddleWare {
trace.Middleware(),
metric.Middleware(),
requestid.Middleware(),
log.Middleware(),
session.Middleware(),
csrf.Middleware(),
orm.Middleware(pingSkipper),
notification.Middleware(pingSkipper), // notification must ahead of transaction ensure the DB transaction execution complete
transaction.Middleware(dbTxSkippers...),
artifactinfo.Middleware(),
security.Middleware(pingSkipper),
log.Middleware(), // log middleware should be after the security middleware so that the user info can be logged
security.UnauthorizedMiddleware(),
readonly.Middleware(readonlySkippers...),
}
Expand Down
63 changes: 62 additions & 1 deletion src/server/middleware/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
package log

import (
"io"
"net/http"

"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log"
tracelib "github.com/goharbor/harbor/src/lib/trace"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/server/middleware"
)

Expand All @@ -40,6 +45,62 @@ func Middleware() func(http.Handler) http.Handler {
r = r.WithContext(ctx)
}

next.ServeHTTP(w, r)
e := &commonevent.Metadata{
Ctx: r.Context(),
Username: "unknown",
RequestMethod: r.Method,
RequestURL: r.URL.String(),
}
if matched, resName := e.PreCheck(); matched {
lib.NopCloseRequest(r)
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read request body", http.StatusInternalServerError)
return
}
requestContent := string(body)
if secCtx, ok := security.FromContext(r.Context()); ok {
e.Username = secCtx.GetUsername()
}
rw := &ResponseWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}
next.ServeHTTP(rw, r)

// Add information in the response
e.ResourceName = resName
e.RequestPayload = requestContent
e.ResponseCode = rw.statusCode

// Need to parse the Location header to get the resource ID on creating resource
if e.RequestMethod == http.MethodPost {
e.ResponseLocation = rw.header.Get("Location")
}

notification.AddEvent(e.Ctx, e, true)
} else {
next.ServeHTTP(w, r)
}

})
}

// ResponseWriter wrapper to HTTP response to get the statusCode and response content
type ResponseWriter struct {
http.ResponseWriter
statusCode int
header http.Header
}

// WriteHeader write header info
func (rw *ResponseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}

// Header get header info
func (rw *ResponseWriter) Header() http.Header {
rw.header = rw.ResponseWriter.Header()
return rw.ResponseWriter.Header()
}

0 comments on commit 67d065e

Please sign in to comment.