Files
beelzebub/parser/configurations_parser.go
Mario Candela c032489522 Feat: add MCP honeypot support (#199)
* Add MCP honeypot

* Add http headers in plain text

* Improve code coverage

* Refactor README.md
2025-07-01 23:07:49 +02:00

202 lines
6.8 KiB
Go

// Package parser is responsible for parsing the configurations of the core and honeypot service
package parser
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
// BeelzebubCoreConfigurations is the struct that contains the configurations of the core
type BeelzebubCoreConfigurations struct {
Core struct {
Logging Logging `yaml:"logging"`
Tracings Tracings `yaml:"tracings"`
Prometheus Prometheus `yaml:"prometheus"`
BeelzebubCloud BeelzebubCloud `yaml:"beelzebub-cloud"`
}
}
// Logging is the struct that contains the configurations of the logging
type Logging struct {
Debug bool `yaml:"debug"`
DebugReportCaller bool `yaml:"debugReportCaller"`
LogDisableTimestamp bool `yaml:"logDisableTimestamp"`
LogsPath string `yaml:"logsPath,omitempty"`
}
// Tracings is the struct that contains the configurations of the tracings
type Tracings struct {
RabbitMQ `yaml:"rabbit-mq"`
}
type BeelzebubCloud struct {
Enabled bool `yaml:"enabled"`
URI string `yaml:"uri"`
AuthToken string `yaml:"auth-token"`
}
type RabbitMQ struct {
Enabled bool `yaml:"enabled"`
URI string `yaml:"uri"`
}
type Prometheus struct {
Path string `yaml:"path"`
Port string `yaml:"port"`
}
type Plugin struct {
OpenAISecretKey string `yaml:"openAISecretKey"`
Host string `yaml:"host"`
LLMModel string `yaml:"llmModel"`
LLMProvider string `yaml:"llmProvider"`
Prompt string `yaml:"prompt"`
}
// BeelzebubServiceConfiguration is the struct that contains the configurations of the honeypot service
type BeelzebubServiceConfiguration struct {
ApiVersion string `yaml:"apiVersion"`
Protocol string `yaml:"protocol"`
Address string `yaml:"address"`
Commands []Command `yaml:"commands"`
Tools []Tool `yaml:"tools"`
FallbackCommand Command `yaml:"fallbackCommand"`
ServerVersion string `yaml:"serverVersion"`
ServerName string `yaml:"serverName"`
DeadlineTimeoutSeconds int `yaml:"deadlineTimeoutSeconds"`
PasswordRegex string `yaml:"passwordRegex"`
Description string `yaml:"description"`
Banner string `yaml:"banner"`
Plugin Plugin `yaml:"plugin"`
TLSCertPath string `yaml:"tlsCertPath"`
TLSKeyPath string `yaml:"tlsKeyPath"`
}
// Command is the struct that contains the configurations of the commands
type Command struct {
RegexStr string `yaml:"regex"`
Regex *regexp.Regexp `yaml:"-"` // This field is parsed, not stored in the config itself.
Handler string `yaml:"handler"`
Headers []string `yaml:"headers"`
StatusCode int `yaml:"statusCode"`
Plugin string `yaml:"plugin"`
Name string `yaml:"name"`
}
// Tool is the struct that contains the configurations of the MCP Honeypot
type Tool struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Params []Param `yaml:"params"`
Handler string `yaml:"handler"`
}
// Param is the struct that contains the configurations of the parameters of the tools
type Param struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
}
type configurationsParser struct {
configurationsCorePath string
configurationsServicesDirectory string
readFileBytesByFilePathDependency ReadFileBytesByFilePath
gelAllFilesNameByDirNameDependency GelAllFilesNameByDirName
}
type ReadFileBytesByFilePath func(filePath string) ([]byte, error)
type GelAllFilesNameByDirName func(dirName string) ([]string, error)
// Init Parser, return a configurationsParser and use the D.I. Pattern to inject the dependencies
func Init(configurationsCorePath, configurationsServicesDirectory string) *configurationsParser {
return &configurationsParser{
configurationsCorePath: configurationsCorePath,
configurationsServicesDirectory: configurationsServicesDirectory,
readFileBytesByFilePathDependency: readFileBytesByFilePath,
gelAllFilesNameByDirNameDependency: gelAllFilesNameByDirName,
}
}
// ReadConfigurationsCore is the method that reads the configurations of the core from files
func (bp configurationsParser) ReadConfigurationsCore() (*BeelzebubCoreConfigurations, error) {
buf, err := bp.readFileBytesByFilePathDependency(bp.configurationsCorePath)
if err != nil {
return nil, fmt.Errorf("in file %s: %v", bp.configurationsCorePath, err)
}
beelzebubConfiguration := &BeelzebubCoreConfigurations{}
err = yaml.Unmarshal(buf, beelzebubConfiguration)
if err != nil {
return nil, fmt.Errorf("in file %s: %v", bp.configurationsCorePath, err)
}
return beelzebubConfiguration, nil
}
// ReadConfigurationsServices is the method that reads the configurations of the honeypot services from files
func (bp configurationsParser) ReadConfigurationsServices() ([]BeelzebubServiceConfiguration, error) {
services, err := bp.gelAllFilesNameByDirNameDependency(bp.configurationsServicesDirectory)
if err != nil {
return nil, fmt.Errorf("in directory %s: %v", bp.configurationsServicesDirectory, err)
}
var servicesConfiguration []BeelzebubServiceConfiguration
for _, servicesName := range services {
filePath := filepath.Join(bp.configurationsServicesDirectory, servicesName)
buf, err := bp.readFileBytesByFilePathDependency(filePath)
if err != nil {
return nil, fmt.Errorf("in file %s: %v", filePath, err)
}
beelzebubServiceConfiguration := &BeelzebubServiceConfiguration{}
err = yaml.Unmarshal(buf, beelzebubServiceConfiguration)
if err != nil {
return nil, fmt.Errorf("in file %s: %v", filePath, err)
}
log.Debug(beelzebubServiceConfiguration)
if err := beelzebubServiceConfiguration.CompileCommandRegex(); err != nil {
return nil, fmt.Errorf("in file %s: invalid regex: %v", filePath, err)
}
servicesConfiguration = append(servicesConfiguration, *beelzebubServiceConfiguration)
}
return servicesConfiguration, nil
}
// CompileCommandRegex is the method that compiles the regular expression for each configured Command.
func (c *BeelzebubServiceConfiguration) CompileCommandRegex() error {
for i, command := range c.Commands {
if command.RegexStr != "" {
rex, err := regexp.Compile(command.RegexStr)
if err != nil {
return err
}
c.Commands[i].Regex = rex
}
}
return nil
}
func gelAllFilesNameByDirName(dirName string) ([]string, error) {
files, err := os.ReadDir(dirName)
if err != nil {
return nil, err
}
var filesName []string
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".yaml") {
filesName = append(filesName, file.Name())
}
}
return filesName, nil
}
func readFileBytesByFilePath(filePath string) ([]byte, error) {
return os.ReadFile(filePath)
}