143 lines
3.9 KiB
Go
143 lines
3.9 KiB
Go
package waf
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/crowdsecurity/coraza/v3/experimental"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
URIHeaderName = "X-Crowdsec-Waf-Uri"
|
|
VerbHeaderName = "X-Crowdsec-Waf-Verb"
|
|
HostHeaderName = "X-Crowdsec-Waf-Host"
|
|
IPHeaderName = "X-Crowdsec-Waf-Ip"
|
|
)
|
|
|
|
// 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 experimental.FullTransaction
|
|
ResponseChannel chan WaapTempResponse
|
|
IsInBand bool
|
|
IsOutBand bool
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
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),
|
|
}, nil
|
|
}
|