mirror of
https://github.com/goharbor/harbor
synced 2025-04-16 18:08:01 +00:00
Add middleware for audit log (#21376)
Add middleware for audit log ext Signed-off-by: stonezdj <stone.zhang@broadcom.com>
This commit is contained in:
parent
b0545c05fd
commit
67654f26bf
89
src/controller/event/metadata/commonevent/model.go
Normal file
89
src/controller/event/metadata/commonevent/model.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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) PreCheckMetadata() (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, ""
|
||||
}
|
|
@ -90,7 +90,6 @@ func MiddleWares() []web.MiddleWare {
|
|||
trace.Middleware(),
|
||||
metric.Middleware(),
|
||||
requestid.Middleware(),
|
||||
log.Middleware(),
|
||||
session.Middleware(),
|
||||
csrf.Middleware(),
|
||||
orm.Middleware(pingSkipper),
|
||||
|
@ -98,6 +97,7 @@ func MiddleWares() []web.MiddleWare {
|
|||
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...),
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -40,6 +45,61 @@ 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.PreCheckMetadata(); 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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user