mirror of
https://github.com/mariocandela/beelzebub.git
synced 2025-07-01 18:47:26 -04:00
Feat: Add FallbackCommand for HTTP Strategy, refactor packages strategies (#175)
Add FallbackCommand for HTTP Strategy, refactor packages strategies, improve histories implementations.
This commit is contained in:
186
protocols/strategies/HTTP/http.go
Normal file
186
protocols/strategies/HTTP/http.go
Normal file
@ -0,0 +1,186 @@
|
||||
package HTTP
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type HTTPStrategy struct{}
|
||||
|
||||
type httpResponse struct {
|
||||
StatusCode int
|
||||
Headers []string
|
||||
Body string
|
||||
}
|
||||
|
||||
func (httpStrategy HTTPStrategy) Init(servConf parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
||||
serverMux := http.NewServeMux()
|
||||
|
||||
serverMux.HandleFunc("/", func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
traceRequest(request, tr, servConf.Description)
|
||||
var matched bool
|
||||
var resp httpResponse
|
||||
var err error
|
||||
for _, command := range servConf.Commands {
|
||||
var err error
|
||||
matched, err = regexp.MatchString(command.Regex, request.RequestURI)
|
||||
if err != nil {
|
||||
log.Errorf("error parsing regex: %s, %s", command.Regex, err.Error())
|
||||
resp.StatusCode = 500
|
||||
resp.Body = "500 Internal Server Error"
|
||||
continue
|
||||
}
|
||||
|
||||
if matched {
|
||||
resp, err = buildHTTPResponse(servConf, command, request)
|
||||
if err != nil {
|
||||
log.Errorf("error building http response: %s: %v", request.RequestURI, err)
|
||||
resp.StatusCode = 500
|
||||
resp.Body = "500 Internal Server Error"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// If none of the main commands matched, and we have a fallback command configured, process it here.
|
||||
// The regexp is ignored for fallback commands, as they are catch-all for any request.
|
||||
if !matched {
|
||||
command := servConf.FallbackCommand
|
||||
if command.Handler != "" || command.Plugin != "" {
|
||||
resp, err = buildHTTPResponse(servConf, command, request)
|
||||
if err != nil {
|
||||
log.Errorf("error building http response: %s: %v", request.RequestURI, err)
|
||||
resp.StatusCode = 500
|
||||
resp.Body = "500 Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
setResponseHeaders(responseWriter, resp.Headers, resp.StatusCode)
|
||||
fmt.Fprint(responseWriter, resp.Body)
|
||||
|
||||
})
|
||||
go func() {
|
||||
var err error
|
||||
// Launch a TLS supporting server if we are supplied a TLS Key and Certificate.
|
||||
// If relative paths are supplied, they are relative to the CWD of the binary.
|
||||
// The can be self-signed, only the client will validate this (or not).
|
||||
if servConf.TLSKeyPath != "" && servConf.TLSCertPath != "" {
|
||||
err = http.ListenAndServeTLS(servConf.Address, servConf.TLSCertPath, servConf.TLSKeyPath, serverMux)
|
||||
} else {
|
||||
err = http.ListenAndServe(servConf.Address, serverMux)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("error during init HTTP Protocol: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"port": servConf.Address,
|
||||
"commands": len(servConf.Commands),
|
||||
}).Infof("Init service: %s", servConf.Description)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildHTTPResponse(servConf parser.BeelzebubServiceConfiguration, command parser.Command, request *http.Request) (httpResponse, error) {
|
||||
resp := httpResponse{
|
||||
Body: command.Handler,
|
||||
Headers: command.Headers,
|
||||
StatusCode: command.StatusCode,
|
||||
}
|
||||
|
||||
if command.Plugin == plugins.LLMPluginName {
|
||||
llmProvider, err := plugins.FromStringToLLMProvider(servConf.Plugin.LLMProvider)
|
||||
if err != nil {
|
||||
log.Errorf("error: %v", err)
|
||||
resp.Body = "404 Not Found!"
|
||||
return resp, err
|
||||
}
|
||||
|
||||
llmHoneypot := plugins.LLMHoneypot{
|
||||
Histories: make([]plugins.Message, 0),
|
||||
OpenAIKey: servConf.Plugin.OpenAISecretKey,
|
||||
Protocol: tracer.HTTP,
|
||||
Host: servConf.Plugin.Host,
|
||||
Model: servConf.Plugin.LLMModel,
|
||||
Provider: llmProvider,
|
||||
CustomPrompt: servConf.Plugin.Prompt,
|
||||
}
|
||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||
command := fmt.Sprintf("%s %s", request.Method, request.RequestURI)
|
||||
|
||||
completions, err := llmHoneypotInstance.ExecuteModel(command)
|
||||
if err != nil {
|
||||
resp.Body = "404 Not Found!"
|
||||
return resp, fmt.Errorf("ExecuteModel error: %s, %v", command, err)
|
||||
}
|
||||
resp.Body = completions
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func traceRequest(request *http.Request, tr tracer.Tracer, HoneypotDescription string) {
|
||||
bodyBytes, err := io.ReadAll(request.Body)
|
||||
body := ""
|
||||
if err == nil {
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
host, port, _ := net.SplitHostPort(request.RemoteAddr)
|
||||
|
||||
event := tracer.Event{
|
||||
Msg: "HTTP New request",
|
||||
RequestURI: request.RequestURI,
|
||||
Protocol: tracer.HTTP.String(),
|
||||
HTTPMethod: request.Method,
|
||||
Body: body,
|
||||
HostHTTPRequest: request.Host,
|
||||
UserAgent: request.UserAgent(),
|
||||
Cookies: mapCookiesToString(request.Cookies()),
|
||||
Headers: request.Header,
|
||||
Status: tracer.Stateless.String(),
|
||||
RemoteAddr: request.RemoteAddr,
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
ID: uuid.New().String(),
|
||||
Description: HoneypotDescription,
|
||||
}
|
||||
// Capture the TLS details from the request, if provided.
|
||||
if request.TLS != nil {
|
||||
event.Msg = "HTTPS New Request"
|
||||
event.TLSServerName = request.TLS.ServerName
|
||||
}
|
||||
tr.TraceEvent(event)
|
||||
}
|
||||
|
||||
func mapCookiesToString(cookies []*http.Cookie) string {
|
||||
cookiesString := ""
|
||||
|
||||
for _, cookie := range cookies {
|
||||
cookiesString += cookie.String()
|
||||
}
|
||||
|
||||
return cookiesString
|
||||
}
|
||||
|
||||
func setResponseHeaders(responseWriter http.ResponseWriter, headers []string, statusCode int) {
|
||||
for _, headerStr := range headers {
|
||||
keyValue := strings.Split(headerStr, ":")
|
||||
if len(keyValue) > 1 {
|
||||
responseWriter.Header().Add(keyValue[0], keyValue[1])
|
||||
}
|
||||
}
|
||||
// http.StatusText(statusCode): empty string if the code is unknown.
|
||||
if len(http.StatusText(statusCode)) > 0 {
|
||||
responseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user