package HTTP import ( "fmt" "io" "net" "net/http" "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) { var matched bool var resp httpResponse var err error for _, command := range servConf.Commands { var err error matched = command.Regex.MatchString(request.RequestURI) if matched { resp, err = buildHTTPResponse(servConf, tr, 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, tr, 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, tr tracer.Tracer, command parser.Command, request *http.Request) (httpResponse, error) { resp := httpResponse{ Body: command.Handler, Headers: command.Headers, StatusCode: command.StatusCode, } traceRequest(request, tr, command, servConf.Description) 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, command parser.Command, 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, Handler: command.Name, } // 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) } }