crowdsec/pkg/waf/request.go
2023-11-29 16:23:49 +01:00

162 lines
4.5 KiB
Go

package waf
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
const (
URIHeaderName = "X-Crowdsec-Waap-Uri"
VerbHeaderName = "X-Crowdsec-Waap-Verb"
HostHeaderName = "X-Crowdsec-Waap-Host"
IPHeaderName = "X-Crowdsec-Waap-Ip"
APIKeyHeaderName = "X-Crowdsec-Waap-Api-Key"
)
// type ResponseRequest struct {
// UUID string
// Tx corazatypes.Transaction
// Interruption *corazatypes.Interruption
// Err error
// SendEvents bool
// }
// func NewResponseRequest(Tx experimental.FullTransaction, in *corazatypes.Interruption, UUID string, err error) ResponseRequest {
// return ResponseRequest{
// UUID: UUID,
// Tx: Tx,
// Interruption: in,
// Err: err,
// SendEvents: true,
// }
// }
// func (r *ResponseRequest) SetRemediation(remediation string) error {
// if r.Interruption == nil {
// return nil
// }
// r.Interruption.Action = remediation
// return nil
// }
// func (r *ResponseRequest) SetRemediationByID(ID int, remediation string) error {
// if r.Interruption == nil {
// return nil
// }
// if r.Interruption.RuleID == ID {
// r.Interruption.Action = remediation
// }
// return nil
// }
// func (r *ResponseRequest) CancelEvent() error {
// // true by default
// r.SendEvents = false
// return nil
// }
type ParsedRequest struct {
RemoteAddr string
Host string
ClientIP string
URI string
Args url.Values
ClientHost string
Headers http.Header
URL *url.URL
Method string
Proto string
Body []byte
TransferEncoding []string
UUID string
Tx ExtendedTransaction
ResponseChannel chan WaapTempResponse
IsInBand bool
IsOutBand bool
WaapEngine string
RemoteAddrNormalized string
}
// Generate a ParsedRequest from a http.Request. ParsedRequest can be consumed by the Waap Engine
func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) {
var err error
body := make([]byte, 0)
if r.Body != nil {
body, err = io.ReadAll(r.Body)
if err != nil {
return ParsedRequest{}, fmt.Errorf("unable to read body: %s", err)
}
}
// the real source of the request is set in 'x-client-ip'
clientIP := r.Header.Get(IPHeaderName)
if clientIP == "" {
return ParsedRequest{}, fmt.Errorf("Missing '%s' header", IPHeaderName)
}
// the real target Host of the request is set in 'x-client-host'
clientHost := r.Header.Get(HostHeaderName)
if clientHost == "" {
return ParsedRequest{}, fmt.Errorf("Missing '%s' header", HostHeaderName)
}
// the real URI of the request is set in 'x-client-uri'
clientURI := r.Header.Get(URIHeaderName)
if clientURI == "" {
return ParsedRequest{}, fmt.Errorf("Missing '%s' header", URIHeaderName)
}
// the real VERB of the request is set in 'x-client-uri'
clientMethod := r.Header.Get(VerbHeaderName)
if clientMethod == "" {
return ParsedRequest{}, fmt.Errorf("Missing '%s' header", VerbHeaderName)
}
// delete those headers before coraza process the request
delete(r.Header, IPHeaderName)
delete(r.Header, HostHeaderName)
delete(r.Header, URIHeaderName)
delete(r.Header, VerbHeaderName)
parsedURL, err := url.Parse(clientURI)
if err != nil {
return ParsedRequest{}, fmt.Errorf("unable to parse url '%s': %s", clientURI, err)
}
RemoteAddrNormalized := ""
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
log.Errorf("Invalid waap remote IP source %v: %s", r.RemoteAddr, err.Error())
RemoteAddrNormalized = r.RemoteAddr
} else {
ip := net.ParseIP(host)
if ip == nil {
log.Errorf("Invalid waap remote IP address source %v: %s", r.RemoteAddr, err.Error())
RemoteAddrNormalized = r.RemoteAddr
}
RemoteAddrNormalized = ip.String()
}
return ParsedRequest{
RemoteAddr: r.RemoteAddr,
UUID: uuid.New().String(),
ClientHost: clientHost,
ClientIP: clientIP,
URI: parsedURL.Path,
Method: clientMethod,
Host: r.Host,
Headers: r.Header,
URL: r.URL,
Proto: r.Proto,
Body: body,
Args: parsedURL.Query(), //TODO: Check if there's not potential bypass as it excludes malformed args
TransferEncoding: r.TransferEncoding,
ResponseChannel: make(chan WaapTempResponse),
RemoteAddrNormalized: RemoteAddrNormalized,
}, nil
}