2021-10-15 10:05:00 -05:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-01-21 15:30:09 -05:00
|
|
|
"github.com/didip/tollbooth/v6"
|
2021-10-15 10:05:00 -05:00
|
|
|
"github.com/gorilla/mux"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
|
|
|
"zotregistry.io/zot/pkg/log"
|
2021-10-15 10:05:00 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type statusWriter struct {
|
|
|
|
http.ResponseWriter
|
|
|
|
status int
|
|
|
|
length int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *statusWriter) WriteHeader(status int) {
|
|
|
|
w.status = status
|
|
|
|
w.ResponseWriter.WriteHeader(status)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *statusWriter) Write(b []byte) (int, error) {
|
|
|
|
if w.status == 0 {
|
2022-01-21 15:30:09 -05:00
|
|
|
w.status = http.StatusOK
|
2021-10-15 10:05:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
n, err := w.ResponseWriter.Write(b)
|
|
|
|
w.length += n
|
|
|
|
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
2022-01-21 15:30:09 -05:00
|
|
|
// RateLimiter limits handling of incoming requests.
|
|
|
|
func RateLimiter(ctlr *Controller, rate int) mux.MiddlewareFunc {
|
|
|
|
ctlr.Log.Info().Int("rate", rate).Msg("ratelimiter enabled")
|
|
|
|
|
|
|
|
limiter := tollbooth.NewLimiter(float64(rate), nil)
|
|
|
|
limiter.SetMessage(http.StatusText(http.StatusTooManyRequests)).
|
|
|
|
SetStatusCode(http.StatusTooManyRequests).
|
|
|
|
SetOnLimitReached(nil)
|
|
|
|
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return tollbooth.LimitHandler(limiter, next)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MethodRateLimiter limits handling of incoming requests.
|
|
|
|
func MethodRateLimiter(ctlr *Controller, method string, rate int) mux.MiddlewareFunc {
|
|
|
|
ctlr.Log.Info().Str("method", method).Int("rate", rate).Msg("per-method ratelimiter enabled")
|
|
|
|
|
|
|
|
limiter := tollbooth.NewLimiter(float64(rate), nil)
|
|
|
|
limiter.SetMethods([]string{method}).
|
|
|
|
SetMessage(http.StatusText(http.StatusTooManyRequests)).
|
|
|
|
SetStatusCode(http.StatusTooManyRequests).
|
|
|
|
SetOnLimitReached(nil)
|
|
|
|
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return tollbooth.LimitHandler(limiter, next)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-15 10:05:00 -05:00
|
|
|
// SessionLogger logs session details.
|
2021-12-13 14:23:31 -05:00
|
|
|
func SessionLogger(ctlr *Controller) mux.MiddlewareFunc {
|
|
|
|
logger := ctlr.Log.With().Str("module", "http").Logger()
|
2021-10-15 10:05:00 -05:00
|
|
|
|
|
|
|
return func(next http.Handler) http.Handler {
|
2021-12-13 14:23:31 -05:00
|
|
|
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
2021-10-15 10:05:00 -05:00
|
|
|
// Start timer
|
|
|
|
start := time.Now()
|
2021-12-13 14:23:31 -05:00
|
|
|
path := request.URL.Path
|
|
|
|
raw := request.URL.RawQuery
|
2021-10-15 10:05:00 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
stwr := statusWriter{ResponseWriter: response}
|
2021-10-15 10:05:00 -05:00
|
|
|
|
|
|
|
// Process request
|
2021-12-13 14:23:31 -05:00
|
|
|
next.ServeHTTP(&stwr, request)
|
2021-10-15 10:05:00 -05:00
|
|
|
|
|
|
|
// Stop timer
|
|
|
|
end := time.Now()
|
|
|
|
latency := end.Sub(start)
|
2022-10-18 22:46:06 -05:00
|
|
|
latency = latency.Truncate(time.Second)
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
clientIP := request.RemoteAddr
|
|
|
|
method := request.Method
|
2021-10-15 10:05:00 -05:00
|
|
|
headers := map[string][]string{}
|
2021-12-13 14:23:31 -05:00
|
|
|
log := logger.Info()
|
|
|
|
for key, value := range request.Header {
|
2021-10-15 10:05:00 -05:00
|
|
|
if key == "Authorization" { // anonymize from logs
|
2021-12-13 14:23:31 -05:00
|
|
|
s := strings.SplitN(value[0], " ", 2) //nolint:gomnd
|
2021-10-15 10:05:00 -05:00
|
|
|
if len(s) == 2 && strings.EqualFold(s[0], "basic") {
|
|
|
|
b, err := base64.StdEncoding.DecodeString(s[1])
|
|
|
|
if err == nil {
|
2021-12-13 14:23:31 -05:00
|
|
|
pair := strings.SplitN(string(b), ":", 2) //nolint:gomnd
|
2022-10-05 05:21:14 -05:00
|
|
|
//nolint:gomnd
|
2021-10-15 10:05:00 -05:00
|
|
|
if len(pair) == 2 {
|
2021-12-13 14:23:31 -05:00
|
|
|
log = log.Str("username", pair[0])
|
2021-10-15 10:05:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
value = []string{"******"}
|
|
|
|
}
|
|
|
|
headers[key] = value
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
statusCode := stwr.status
|
|
|
|
bodySize := stwr.length
|
2021-10-15 10:05:00 -05:00
|
|
|
if raw != "" {
|
|
|
|
path = path + "?" + raw
|
|
|
|
}
|
|
|
|
|
|
|
|
if path != "/v2/metrics" {
|
|
|
|
// In order to test metrics feture,the instrumentation related to node exporter
|
|
|
|
// should be handled by node exporter itself (ex: latency)
|
2021-12-13 14:23:31 -05:00
|
|
|
monitoring.IncHTTPConnRequests(ctlr.Metrics, method, strconv.Itoa(statusCode))
|
|
|
|
monitoring.ObserveHTTPRepoLatency(ctlr.Metrics, path, latency) // summary
|
|
|
|
monitoring.ObserveHTTPMethodLatency(ctlr.Metrics, method, latency) // histogram
|
2021-10-15 10:05:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Str("clientIP", clientIP).
|
|
|
|
Str("method", method).
|
|
|
|
Str("path", path).
|
|
|
|
Int("statusCode", statusCode).
|
|
|
|
Str("latency", latency.String()).
|
|
|
|
Int("bodySize", bodySize).
|
|
|
|
Interface("headers", headers).
|
|
|
|
Msg("HTTP API")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func SessionAuditLogger(audit *log.Logger) mux.MiddlewareFunc {
|
|
|
|
return func(next http.Handler) http.Handler {
|
2021-12-13 14:23:31 -05:00
|
|
|
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
|
|
path := request.URL.Path
|
|
|
|
raw := request.URL.RawQuery
|
2021-10-15 10:05:00 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
statusWr := statusWriter{ResponseWriter: response}
|
2021-10-15 10:05:00 -05:00
|
|
|
|
|
|
|
// Process request
|
2021-12-13 14:23:31 -05:00
|
|
|
next.ServeHTTP(&statusWr, request)
|
2021-10-15 10:05:00 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
clientIP := request.RemoteAddr
|
|
|
|
method := request.Method
|
2021-10-15 10:05:00 -05:00
|
|
|
username := ""
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
for key, value := range request.Header {
|
2021-10-15 10:05:00 -05:00
|
|
|
if key == "Authorization" { // anonymize from logs
|
2021-12-13 14:23:31 -05:00
|
|
|
s := strings.SplitN(value[0], " ", 2) //nolint:gomnd
|
2021-10-15 10:05:00 -05:00
|
|
|
if len(s) == 2 && strings.EqualFold(s[0], "basic") {
|
|
|
|
b, err := base64.StdEncoding.DecodeString(s[1])
|
|
|
|
if err == nil {
|
2021-12-13 14:23:31 -05:00
|
|
|
pair := strings.SplitN(string(b), ":", 2) //nolint:gomnd
|
|
|
|
if len(pair) == 2 { //nolint:gomnd
|
2021-10-15 10:05:00 -05:00
|
|
|
username = pair[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
statusCode := statusWr.status
|
2021-10-15 10:05:00 -05:00
|
|
|
if raw != "" {
|
|
|
|
path = path + "?" + raw
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method == http.MethodPost || method == http.MethodPut ||
|
|
|
|
method == http.MethodPatch || method == http.MethodDelete) &&
|
|
|
|
(statusCode == http.StatusOK || statusCode == http.StatusCreated || statusCode == http.StatusAccepted) {
|
|
|
|
audit.Info().
|
|
|
|
Str("clientIP", clientIP).
|
|
|
|
Str("subject", username).
|
|
|
|
Str("action", method).
|
|
|
|
Str("object", path).
|
|
|
|
Int("status", statusCode).
|
|
|
|
Msg("HTTP API Audit")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|