mirror of
https://github.com/mariocandela/beelzebub.git
synced 2025-07-01 18:47:26 -04:00
Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
0b5486964b | |||
fa472effb9 | |||
628e20e01f | |||
0d4aa5f52e | |||
58f27ab076 | |||
c4132f2d75 | |||
f4ec6dcefb | |||
a1e96738fb | |||
cd284877cf | |||
2088163b54 | |||
0af1a05ae9 | |||
5086f5ba08 | |||
e1f82db2d0 | |||
59f40a166b | |||
93d7804ba3 | |||
24b4153e77 | |||
1d90c83678 | |||
67829655f4 | |||
9ad21e138b | |||
8ab11e6ac2 | |||
965942609d | |||
b8d77983ee | |||
b332f85230 | |||
b1de020de8 | |||
05b49051db | |||
3555ea9d3b | |||
d4fe0f96bd | |||
6e26f76c51 | |||
b2a7a527ff | |||
1c650882b6 | |||
ada0a9b8f0 | |||
ccd160f7b0 | |||
67d9ea7168 | |||
b441f8f9ab | |||
0b9aa8b965 | |||
c7bd863b36 | |||
e2d1cc6087 | |||
5bb7a96b39 | |||
ab7cefdb18 | |||
34159ca06f | |||
fd2bbe6be6 | |||
9076dfa47c | |||
2b90c4a1f5 | |||
90004e7d84 | |||
9e042e33f5 | |||
5e5d0494a9 | |||
d77aa0c8a0 | |||
3733c902b9 | |||
07ffdd839f | |||
1f48f4dff5 | |||
88a96a7efd | |||
34a80b06f3 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -51,6 +51,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Start integration test dependencies
|
||||
run: |
|
||||
make test.dependencies.start
|
||||
|
@ -2,8 +2,7 @@ FROM golang:alpine AS builder
|
||||
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64
|
||||
GOOS=linux
|
||||
|
||||
RUN apk add git
|
||||
|
||||
@ -27,4 +26,4 @@ FROM scratch
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /dist/main /
|
||||
|
||||
ENTRYPOINT ["/main"]
|
||||
ENTRYPOINT ["/main"]
|
||||
|
48
README.md
48
README.md
@ -1,16 +1,21 @@
|
||||
# Beelzebub
|
||||
|
||||
[](https://github.com/mariocandela/beelzebub/actions/workflows/ci.yml) [](https://github.com/mariocandela/beelzebub/actions/workflows/docker-image.yml) [](https://github.com/mariocandela/beelzebub/actions/workflows/codeql.yml)
|
||||
[](https://goreportcard.com/report/github.com/mariocandela/beelzebub/v3)
|
||||
[](https://codecov.io/gh/mariocandela/beelzebub)
|
||||
[](https://pkg.go.dev/github.com/mariocandela/beelzebub/v3)
|
||||
[](https://github.com/avelino/awesome-go)
|
||||
|
||||
## Overview
|
||||
|
||||
Beelzebub is an advanced honeypot framework designed to provide a highly secure environment for detecting and analyzing cyber attacks. It offers a low code approach for easy implementation and utilizes virtualization techniques powered by OpenAI Generative Pre-trained Transformer.
|
||||
Beelzebub is an advanced honeypot framework designed to provide a highly secure environment for detecting and analyzing cyber attacks. It offers a low code approach for easy implementation and uses AI to mimic the behavior of a high-interaction honeypot.
|
||||
|
||||
<img src="https://beelzebub.netlify.app/go-beelzebub.png" alt="Beelzebub Logo" width="200"/>
|
||||
|
||||
## OpenAI GPT Integration
|
||||
## LLM Honeypot
|
||||
|
||||
[](https://asciinema.org/a/665295)
|
||||
|
||||
Learn how to integrate Beelzebub with OpenAI GPT-3 by referring to our comprehensive guide on Medium: [Medium Article](https://medium.com/@mario.candela.personal/how-to-build-a-highly-effective-honeypot-with-beelzebub-and-chatgpt-a2f0f05b3e1)
|
||||
|
||||
## Telegram Bot for Real-Time Attacks
|
||||
|
||||
@ -90,14 +95,17 @@ $ make test.unit
|
||||
To run integration tests:
|
||||
|
||||
```bash
|
||||
$ make test.dependencies.start
|
||||
$ make test.integration
|
||||
$ make test.dependencies.down
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
Beelzebub offers a wide range of features to enhance your honeypot environment:
|
||||
|
||||
- OpenAI Generative Pre-trained Transformer act as Linux virtualization
|
||||
- Support for Ollama
|
||||
- Support for OpenAI
|
||||
- SSH Honeypot
|
||||
- HTTP Honeypot
|
||||
- TCP Honeypot
|
||||
@ -203,22 +211,44 @@ commands:
|
||||
|
||||
#### Example SSH Honeypot
|
||||
|
||||
###### Honeypot with GPT-3 on Port 2222
|
||||
###### Honeypot LLM Honeypots
|
||||
|
||||
Example with OpenAI GPT-4:
|
||||
|
||||
```yaml
|
||||
apiVersion: "v1"
|
||||
protocol: "ssh"
|
||||
address: ":2222"
|
||||
description: "SSH interactive ChatGPT"
|
||||
description: "SSH interactive OpenAI GPT-4"
|
||||
commands:
|
||||
- regex: "^(.+)$"
|
||||
plugin: "OpenAIGPTLinuxTerminal"
|
||||
plugin: "LLMHoneypot"
|
||||
serverVersion: "OpenSSH"
|
||||
serverName: "ubuntu"
|
||||
passwordRegex: "^(root|qwerty|Smoker666|123456|jenkins|minecraft|sinus|alex|postgres|Ly123456)$"
|
||||
deadlineTimeoutSeconds: 60
|
||||
plugin:
|
||||
openAPIChatGPTSecretKey: "Your OpenAI Secret Key"
|
||||
llmModel: "gpt4-o"
|
||||
openAISecretKey: "sk-proj-123456"
|
||||
```
|
||||
|
||||
Example with Ollama Llama3:
|
||||
|
||||
```yaml
|
||||
apiVersion: "v1"
|
||||
protocol: "ssh"
|
||||
address: ":2222"
|
||||
description: "SSH Ollama Llama3"
|
||||
commands:
|
||||
- regex: "^(.+)$"
|
||||
plugin: "LLMHoneypot"
|
||||
serverVersion: "OpenSSH"
|
||||
serverName: "ubuntu"
|
||||
passwordRegex: "^(root|qwerty|Smoker666|123456|jenkins|minecraft|sinus|alex|postgres|Ly123456)$"
|
||||
deadlineTimeoutSeconds: 60
|
||||
plugin:
|
||||
llmModel: "llama3"
|
||||
host: "http://example.com/api/chat" #default http://localhost:11434/api/chat
|
||||
```
|
||||
|
||||
###### SSH Honeypot on Port 22
|
||||
@ -255,8 +285,6 @@ passwordRegex: "^(root|qwerty|Smoker666)$"
|
||||
deadlineTimeoutSeconds: 60
|
||||
```
|
||||
|
||||
[](https://asciinema.org/a/604522)
|
||||
|
||||
## Roadmap
|
||||
|
||||
Our future plans for Beelzebub include developing it into a robust PaaS platform.
|
||||
|
@ -3,10 +3,11 @@ package builder
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/protocols"
|
||||
"github.com/mariocandela/beelzebub/protocols/strategies"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||
"github.com/mariocandela/beelzebub/v3/protocols"
|
||||
"github.com/mariocandela/beelzebub/v3/protocols/strategies"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -112,6 +113,21 @@ Honeypot Framework, happy hacking!`)
|
||||
// Init Tracer strategies, and set the trace strategy default HTTP
|
||||
protocolManager := protocols.InitProtocolManager(b.traceStrategy, hypertextTransferProtocolStrategy)
|
||||
|
||||
if b.beelzebubCoreConfigurations.Core.BeelzebubCloud.Enabled {
|
||||
conf := b.beelzebubCoreConfigurations.Core.BeelzebubCloud
|
||||
|
||||
beelzebubCloud := plugins.InitBeelzebubCloud(conf.URI, conf.AuthToken)
|
||||
|
||||
if honeypotsConfiguration, err := beelzebubCloud.GetHoneypotsConfigurations(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if len(honeypotsConfiguration) == 0 {
|
||||
return errors.New("No honeypots configuration found")
|
||||
}
|
||||
b.beelzebubServicesConfiguration = honeypotsConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
for _, beelzebubServiceConfiguration := range b.beelzebubServicesConfiguration {
|
||||
switch beelzebubServiceConfiguration.Protocol {
|
||||
case "http":
|
||||
|
@ -3,8 +3,9 @@ package builder
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -37,6 +38,10 @@ func (d *Director) BuildBeelzebub(beelzebubCoreConfigurations *parser.BeelzebubC
|
||||
}
|
||||
}
|
||||
|
||||
if beelzebubCoreConfigurations.Core.BeelzebubCloud.Enabled {
|
||||
d.builder.setTraceStrategy(d.beelzebubCloudStrategy)
|
||||
}
|
||||
|
||||
return d.builder.build(), nil
|
||||
}
|
||||
|
||||
@ -47,6 +52,27 @@ func (d *Director) standardOutStrategy(event tracer.Event) {
|
||||
}).Info("New Event")
|
||||
}
|
||||
|
||||
func (d *Director) beelzebubCloudStrategy(event tracer.Event) {
|
||||
log.WithFields(log.Fields{
|
||||
"status": event.Status,
|
||||
"event": event,
|
||||
}).Info("New Event")
|
||||
|
||||
conf := d.builder.beelzebubCoreConfigurations.Core.BeelzebubCloud
|
||||
|
||||
beelzebubCloud := plugins.InitBeelzebubCloud(conf.URI, conf.AuthToken)
|
||||
|
||||
result, err := beelzebubCloud.SendEvent(event)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"status": result,
|
||||
"event": event,
|
||||
}).Debug("Event published on beelzebub cloud")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Director) rabbitMQTraceStrategy(event tracer.Event) {
|
||||
log.WithFields(log.Fields{
|
||||
"status": event.Status,
|
||||
|
@ -11,4 +11,7 @@ core:
|
||||
prometheus:
|
||||
path: "/metrics"
|
||||
port: ":2112"
|
||||
|
||||
beelzebub-cloud:
|
||||
enabled: false
|
||||
uri: ""
|
||||
auth-token: ""
|
||||
|
@ -18,41 +18,9 @@ commands:
|
||||
- "Server: Apache/2.4.53 (Debian)"
|
||||
- "X-Powered-By: PHP/7.4.29"
|
||||
statusCode: 200
|
||||
- regex: "^(/wp-login.php|/wp-admin)$"
|
||||
handler:
|
||||
<html>
|
||||
<header>
|
||||
<title>Wordpress 6 test page</title>
|
||||
</header>
|
||||
<body>
|
||||
<form action="" method="post">
|
||||
<label for="uname"><b>Username</b></label>
|
||||
<input type="text" placeholder="Enter Username" name="uname" required>
|
||||
|
||||
<label for="psw"><b>Password</b></label>
|
||||
<input type="password" placeholder="Enter Password" name="psw" required>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
headers:
|
||||
- "Content-Type: text/html"
|
||||
- "Server: Apache/2.4.53 (Debian)"
|
||||
- "X-Powered-By: PHP/7.4.29"
|
||||
statusCode: 200
|
||||
- regex: "^.*$"
|
||||
handler:
|
||||
<html>
|
||||
<header>
|
||||
<title>404</title>
|
||||
</header>
|
||||
<body>
|
||||
<h1>Not found!</h1>
|
||||
</body>
|
||||
</html>
|
||||
headers:
|
||||
- "Content-Type: text/html"
|
||||
- "Server: Apache/2.4.53 (Debian)"
|
||||
- "X-Powered-By: PHP/7.4.29"
|
||||
statusCode: 404
|
||||
plugin: "LLMHoneypot"
|
||||
statusCode: 200
|
||||
plugin:
|
||||
llmModel: "gpt4-o"
|
||||
openAISecretKey: "sk-proj-123456"
|
@ -4,10 +4,10 @@ address: ":2222"
|
||||
description: "SSH interactive ChatGPT"
|
||||
commands:
|
||||
- regex: "^(.+)$"
|
||||
plugin: "OpenAIGPTLinuxTerminal"
|
||||
plugin: "LLMHoneypot"
|
||||
serverVersion: "OpenSSH"
|
||||
serverName: "ubuntu"
|
||||
passwordRegex: "^(root|qwerty|Smoker666|123456|jenkins|minecraft|sinus|alex|postgres|Ly123456)$"
|
||||
deadlineTimeoutSeconds: 60
|
||||
deadlineTimeoutSeconds: 6000
|
||||
plugin:
|
||||
openAPIChatGPTSecretKey: ""
|
||||
llmModel: "llama3"
|
@ -10,6 +10,7 @@ services:
|
||||
- "22:22"
|
||||
- "2222:2222"
|
||||
- "8080:8080"
|
||||
- "8081:8081"
|
||||
- "80:80"
|
||||
- "3306:3306"
|
||||
- "2112:2112" # Prometheus openmetrics
|
||||
|
37
go.mod
37
go.mod
@ -1,37 +1,38 @@
|
||||
module github.com/mariocandela/beelzebub
|
||||
module github.com/mariocandela/beelzebub/v3
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gliderlabs/ssh v0.3.5
|
||||
github.com/go-resty/resty/v2 v2.9.1
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gliderlabs/ssh v0.3.7
|
||||
github.com/go-resty/resty/v2 v2.14.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jarcoal/httpmock v1.3.1
|
||||
github.com/melbahja/goph v1.4.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/rabbitmq/amqp091-go v1.9.0
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/crypto v0.14.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/sftp v1.13.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
)
|
||||
|
124
go.sum
124
go.sum
@ -2,89 +2,92 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
|
||||
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU=
|
||||
github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||
github.com/melbahja/goph v1.4.0 h1:z0PgDbBFe66lRYl3v5dGb9aFgPy0kotuQ37QOwSQFqs=
|
||||
github.com/melbahja/goph v1.4.0/go.mod h1:uG+VfK2Dlhk+O32zFrRlc3kYKTlV6+BtvPWd/kK7U68=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
|
||||
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -93,21 +96,25 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -115,20 +122,21 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@ -2,9 +2,9 @@ package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/mariocandela/beelzebub/builder"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/builder"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
4
main.go
4
main.go
@ -2,8 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/mariocandela/beelzebub/builder"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/builder"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Package parser is responsible for parsing the configurations of the core and honeypot service
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -11,14 +11,17 @@ import (
|
||||
"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"`
|
||||
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"`
|
||||
@ -26,10 +29,16 @@ type Logging struct {
|
||||
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"`
|
||||
@ -40,9 +49,12 @@ type Prometheus struct {
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
OpenAPIChatGPTSecretKey string `yaml:"openAPIChatGPTSecretKey"`
|
||||
OpenAISecretKey string `yaml:"openAISecretKey"`
|
||||
Host string `yaml:"host"`
|
||||
LLMModel string `yaml:"llmModel"`
|
||||
}
|
||||
|
||||
// BeelzebubServiceConfiguration is the struct that contains the configurations of the honeypot service
|
||||
type BeelzebubServiceConfiguration struct {
|
||||
ApiVersion string `yaml:"apiVersion"`
|
||||
Protocol string `yaml:"protocol"`
|
||||
@ -57,6 +69,7 @@ type BeelzebubServiceConfiguration struct {
|
||||
Plugin Plugin `yaml:"plugin"`
|
||||
}
|
||||
|
||||
// Command is the struct that contains the configurations of the commands
|
||||
type Command struct {
|
||||
Regex string `yaml:"regex"`
|
||||
Handler string `yaml:"handler"`
|
||||
@ -86,6 +99,7 @@ func Init(configurationsCorePath, configurationsServicesDirectory string) *confi
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -101,6 +115,7 @@ func (bp configurationsParser) ReadConfigurationsCore() (*BeelzebubCoreConfigura
|
||||
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 {
|
||||
@ -127,7 +142,7 @@ func (bp configurationsParser) ReadConfigurationsServices() ([]BeelzebubServiceC
|
||||
}
|
||||
|
||||
func gelAllFilesNameByDirName(dirName string) ([]string, error) {
|
||||
files, err := ioutil.ReadDir(dirName)
|
||||
files, err := os.ReadDir(dirName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -18,7 +19,11 @@ core:
|
||||
tracings:
|
||||
rabbit-mq:
|
||||
enabled: true
|
||||
uri: "amqp://user:password@localhost/"`)
|
||||
uri: "amqp://user:password@localhost/"
|
||||
beelzebub-cloud:
|
||||
enabled: true
|
||||
uri: "amqp://user:password@localhost/"
|
||||
auth-token: "iejfdjsl-aosdajosoidaj-dunfkjnfkjsdnkn"`)
|
||||
return configurationsCoreBytes, nil
|
||||
}
|
||||
|
||||
@ -48,7 +53,12 @@ commands:
|
||||
- regex: "wp-admin"
|
||||
handler: "login"
|
||||
headers:
|
||||
- "Content-Type: text/html"`)
|
||||
- "Content-Type: text/html"
|
||||
plugin:
|
||||
openAISecretKey: "qwerty"
|
||||
llmModel: "llama3"
|
||||
host: "localhost:1563"
|
||||
`)
|
||||
return beelzebubServiceConfiguration, nil
|
||||
}
|
||||
|
||||
@ -84,6 +94,9 @@ func TestReadConfigurationsCoreValid(t *testing.T) {
|
||||
assert.Equal(t, coreConfigurations.Core.Logging.LogsPath, "./logs")
|
||||
assert.Equal(t, coreConfigurations.Core.Tracings.RabbitMQ.Enabled, true)
|
||||
assert.Equal(t, coreConfigurations.Core.Tracings.RabbitMQ.URI, "amqp://user:password@localhost/")
|
||||
assert.Equal(t, coreConfigurations.Core.BeelzebubCloud.Enabled, true)
|
||||
assert.Equal(t, coreConfigurations.Core.BeelzebubCloud.URI, "amqp://user:password@localhost/")
|
||||
assert.Equal(t, coreConfigurations.Core.BeelzebubCloud.AuthToken, "iejfdjsl-aosdajosoidaj-dunfkjnfkjsdnkn")
|
||||
}
|
||||
|
||||
func TestReadConfigurationsServicesFail(t *testing.T) {
|
||||
@ -104,10 +117,10 @@ func TestReadConfigurationsServicesValid(t *testing.T) {
|
||||
configurationsParser.gelAllFilesNameByDirNameDependency = mockReadDirValid
|
||||
|
||||
beelzebubServicesConfiguration, err := configurationsParser.ReadConfigurationsServices()
|
||||
assert.Nil(t, err)
|
||||
|
||||
firstBeelzebubServiceConfiguration := beelzebubServicesConfiguration[0]
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Protocol, "http")
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.ApiVersion, "v1")
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Address, ":8080")
|
||||
@ -117,4 +130,58 @@ func TestReadConfigurationsServicesValid(t *testing.T) {
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Handler, "login")
|
||||
assert.Equal(t, len(firstBeelzebubServiceConfiguration.Commands[0].Headers), 1)
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Headers[0], "Content-Type: text/html")
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.OpenAISecretKey, "qwerty")
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.LLMModel, "llama3")
|
||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.Host, "localhost:1563")
|
||||
}
|
||||
|
||||
func TestGelAllFilesNameByDirName(t *testing.T) {
|
||||
|
||||
var dir = t.TempDir()
|
||||
|
||||
files, err := gelAllFilesNameByDirName(dir)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(files))
|
||||
}
|
||||
|
||||
func TestGelAllFilesNameByDirNameFiles(t *testing.T) {
|
||||
|
||||
var dir = t.TempDir()
|
||||
|
||||
testFiles := []string{"file1.yaml", "file2.yaml", "file3.txt", "subdir", "file4.yaml"}
|
||||
for _, filename := range testFiles {
|
||||
filePath := dir + "/" + filename
|
||||
file, err := os.Create(filePath)
|
||||
assert.NoError(t, err)
|
||||
file.Close()
|
||||
}
|
||||
|
||||
files, err := gelAllFilesNameByDirName(dir)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(files))
|
||||
}
|
||||
|
||||
func TestGelAllFilesNameByDirNameError(t *testing.T) {
|
||||
|
||||
files, err := gelAllFilesNameByDirName("nosuchfile")
|
||||
|
||||
assert.Nil(t, files)
|
||||
assert.Equal(t, "open nosuchfile: no such file or directory", err.Error())
|
||||
}
|
||||
|
||||
func TestReadFileBytesByFilePath(t *testing.T) {
|
||||
|
||||
var dir = t.TempDir()
|
||||
filePath := dir + "/test.yaml"
|
||||
|
||||
f, err := os.Create(filePath)
|
||||
assert.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
bytes, err := readFileBytesByFilePath(filePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "", string(bytes))
|
||||
}
|
||||
|
100
plugins/beelzebub-cloud.go
Normal file
100
plugins/beelzebub-cloud.go
Normal file
@ -0,0 +1,100 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type beelzebubCloud struct {
|
||||
URI string
|
||||
AuthToken string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
type HoneypotConfigResponseDTO struct {
|
||||
ID string `json:"id"`
|
||||
Config string `json:"config"`
|
||||
TokenID string `json:"tokenId"`
|
||||
LastUpdatedOn string `json:"lastUpdatedOn"`
|
||||
}
|
||||
|
||||
func InitBeelzebubCloud(uri, authToken string) *beelzebubCloud {
|
||||
return &beelzebubCloud{
|
||||
URI: uri,
|
||||
AuthToken: authToken,
|
||||
client: resty.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (beelzebubCloud *beelzebubCloud) SendEvent(event tracer.Event) (bool, error) {
|
||||
requestJson, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if beelzebubCloud.AuthToken == "" {
|
||||
return false, errors.New("authToken is empty")
|
||||
}
|
||||
|
||||
response, err := beelzebubCloud.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(requestJson).
|
||||
SetHeader("Authorization", beelzebubCloud.AuthToken).
|
||||
SetResult(&tracer.Event{}).
|
||||
Post(fmt.Sprintf("%s/events", beelzebubCloud.URI))
|
||||
|
||||
log.Debug(response)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return response.StatusCode() == 200, nil
|
||||
}
|
||||
|
||||
func (beelzebubCloud *beelzebubCloud) GetHoneypotsConfigurations() ([]parser.BeelzebubServiceConfiguration, error) {
|
||||
if beelzebubCloud.AuthToken == "" {
|
||||
return nil, errors.New("authToken is empty")
|
||||
}
|
||||
|
||||
response, err := beelzebubCloud.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Authorization", beelzebubCloud.AuthToken).
|
||||
SetResult([]HoneypotConfigResponseDTO{}).
|
||||
Get(fmt.Sprintf("%s/honeypots", beelzebubCloud.URI))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode() != 200 {
|
||||
return nil, errors.New(fmt.Sprintf("Response code: %v, error: %s", response.StatusCode(), string(response.Body())))
|
||||
}
|
||||
|
||||
var honeypotsConfig []HoneypotConfigResponseDTO
|
||||
|
||||
if err = json.Unmarshal(response.Body(), &honeypotsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var servicesConfiguration = make([]parser.BeelzebubServiceConfiguration, 0)
|
||||
|
||||
for _, honeypotConfig := range honeypotsConfig {
|
||||
var honeypotsConfig parser.BeelzebubServiceConfiguration
|
||||
|
||||
if err = yaml.Unmarshal([]byte(honeypotConfig.Config), &honeypotsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servicesConfiguration = append(servicesConfiguration, honeypotsConfig)
|
||||
}
|
||||
|
||||
log.Debug(servicesConfiguration)
|
||||
|
||||
return servicesConfiguration, nil
|
||||
}
|
230
plugins/beelzebub-cloud_test.go
Normal file
230
plugins/beelzebub-cloud_test.go
Normal file
@ -0,0 +1,230 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildSendEventFailValidation(t *testing.T) {
|
||||
beelzebubCloud := InitBeelzebubCloud("", "")
|
||||
|
||||
_, err := beelzebubCloud.SendEvent(tracer.Event{})
|
||||
|
||||
assert.Equal(t, "authToken is empty", err.Error())
|
||||
}
|
||||
|
||||
func TestBuildSendEventWithResults(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", fmt.Sprintf("%s/events", uri),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &tracer.Event{})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.SendEvent(tracer.Event{})
|
||||
|
||||
//Then
|
||||
assert.Equal(t, true, result)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestBuildSendEventErro(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081/events"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", uri,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, _ := beelzebubCloud.SendEvent(tracer.Event{})
|
||||
|
||||
//Then
|
||||
assert.Equal(t, false, result)
|
||||
}
|
||||
|
||||
func TestGetHoneypotsConfigurationsWithResults(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("GET", fmt.Sprintf("%s/honeypots", uri),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &[]HoneypotConfigResponseDTO{
|
||||
{
|
||||
ID: "123456",
|
||||
Config: "apiVersion: \"v1\"\nprotocol: \"ssh\"\naddress: \":2222\"\ndescription: \"SSH interactive ChatGPT\"\ncommands:\n - regex: \"^(.+)$\"\n plugin: \"LLMHoneypot\"\nserverVersion: \"OpenSSH\"\nserverName: \"ubuntu\"\npasswordRegex: \"^(root|qwerty|Smoker666|123456|jenkins|minecraft|sinus|alex|postgres|Ly123456)$\"\ndeadlineTimeoutSeconds: 60\nplugin:\n llmModel: \"gpt4-o\"\n openAISecretKey: \"1234\"\n",
|
||||
TokenID: "1234567",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.GetHoneypotsConfigurations()
|
||||
|
||||
//Then
|
||||
assert.Equal(t, &[]parser.BeelzebubServiceConfiguration{
|
||||
{
|
||||
ApiVersion: "v1",
|
||||
Protocol: "ssh",
|
||||
Address: ":2222",
|
||||
Description: "SSH interactive ChatGPT",
|
||||
Commands: []parser.Command{
|
||||
{
|
||||
Regex: "^(.+)$",
|
||||
Plugin: "LLMHoneypot",
|
||||
},
|
||||
},
|
||||
ServerVersion: "OpenSSH",
|
||||
ServerName: "ubuntu",
|
||||
PasswordRegex: "^(root|qwerty|Smoker666|123456|jenkins|minecraft|sinus|alex|postgres|Ly123456)$",
|
||||
DeadlineTimeoutSeconds: 60,
|
||||
Plugin: parser.Plugin{
|
||||
LLMModel: "gpt4-o",
|
||||
OpenAISecretKey: "1234",
|
||||
},
|
||||
},
|
||||
}, &result)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetHoneypotsConfigurationsWithErrorValidation(t *testing.T) {
|
||||
//Given
|
||||
beelzebubCloud := InitBeelzebubCloud("", "")
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.GetHoneypotsConfigurations()
|
||||
|
||||
//Then
|
||||
assert.Nil(t, result)
|
||||
assert.Equal(t, "authToken is empty", err.Error())
|
||||
}
|
||||
|
||||
func TestGetHoneypotsConfigurationsWithErrorAPI(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("GET", fmt.Sprintf("%s/honeypots", uri),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.GetHoneypotsConfigurations()
|
||||
|
||||
//Then
|
||||
assert.Nil(t, result)
|
||||
assert.Equal(t, "Response code: 500, error: ", err.Error())
|
||||
}
|
||||
|
||||
func TestGetHoneypotsConfigurationsWithErrorUnmarshal(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("GET", fmt.Sprintf("%s/honeypots", uri),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, "error")
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.GetHoneypotsConfigurations()
|
||||
|
||||
//Then
|
||||
assert.Nil(t, result)
|
||||
assert.Equal(t, "json: cannot unmarshal string into Go value of type []plugins.HoneypotConfigResponseDTO", err.Error())
|
||||
}
|
||||
|
||||
func TestGetHoneypotsConfigurationsWithErrorDeserializeYaml(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
uri := "localhost:8081"
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("GET", fmt.Sprintf("%s/honeypots", uri),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &[]HoneypotConfigResponseDTO{
|
||||
{
|
||||
ID: "123456",
|
||||
Config: "error",
|
||||
TokenID: "1234567",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
beelzebubCloud := InitBeelzebubCloud(uri, "sdjdnklfjndslkjanfk")
|
||||
beelzebubCloud.client = client
|
||||
|
||||
//When
|
||||
result, err := beelzebubCloud.GetHoneypotsConfigurations()
|
||||
|
||||
//Then
|
||||
assert.Nil(t, result)
|
||||
assert.Equal(t, "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `error` into parser.BeelzebubServiceConfiguration", err.Error())
|
||||
}
|
229
plugins/llm-integration.go
Normal file
229
plugins/llm-integration.go
Normal file
@ -0,0 +1,229 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
systemPromptVirtualizeLinuxTerminal = "You will act as an Ubuntu Linux terminal. The user will type commands, and you are to reply with what the terminal should show. Your responses must be contained within a single code block. Do not provide explanations or type commands unless explicitly instructed by the user. Your entire response/output is going to consist of a simple text with \n for new line, and you will NOT wrap it within string md markers"
|
||||
systemPromptVirtualizeHTTPServer = "You will act as an unsecure HTTP Server with multiple vulnerability like aws and git credentials stored into root http directory. The user will send HTTP requests, and you are to reply with what the server should show. Do not provide explanations or type commands unless explicitly instructed by the user."
|
||||
LLMPluginName = "LLMHoneypot"
|
||||
openAIGPTEndpoint = "https://api.openai.com/v1/chat/completions"
|
||||
ollamaEndpoint = "http://localhost:11434/api/chat"
|
||||
)
|
||||
|
||||
type LLMHoneypot struct {
|
||||
Histories []Message
|
||||
OpenAIKey string
|
||||
client *resty.Client
|
||||
Protocol tracer.Protocol
|
||||
Model LLMModel
|
||||
Host string
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
Message Message `json:"message"`
|
||||
Index int `json:"index"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Message Message `json:"message"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
Stream bool `json:"stream"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type Role int
|
||||
|
||||
const (
|
||||
SYSTEM Role = iota
|
||||
USER
|
||||
ASSISTANT
|
||||
)
|
||||
|
||||
func (role Role) String() string {
|
||||
return [...]string{"system", "user", "assistant"}[role]
|
||||
}
|
||||
|
||||
type LLMModel int
|
||||
|
||||
const (
|
||||
LLAMA3 LLMModel = iota
|
||||
GPT4O
|
||||
)
|
||||
|
||||
func FromStringToLLMModel(llmModel string) (LLMModel, error) {
|
||||
switch llmModel {
|
||||
case "llama3":
|
||||
return LLAMA3, nil
|
||||
case "gpt4-o":
|
||||
return GPT4O, nil
|
||||
default:
|
||||
return -1, fmt.Errorf("model %s not found", llmModel)
|
||||
}
|
||||
}
|
||||
|
||||
func InitLLMHoneypot(config LLMHoneypot) *LLMHoneypot {
|
||||
// Inject the dependencies
|
||||
config.client = resty.New()
|
||||
|
||||
return &config
|
||||
}
|
||||
|
||||
func buildPrompt(histories []Message, protocol tracer.Protocol, command string) ([]Message, error) {
|
||||
var messages []Message
|
||||
|
||||
switch protocol {
|
||||
case tracer.SSH:
|
||||
messages = append(messages, Message{
|
||||
Role: SYSTEM.String(),
|
||||
Content: systemPromptVirtualizeLinuxTerminal,
|
||||
})
|
||||
messages = append(messages, Message{
|
||||
Role: USER.String(),
|
||||
Content: "pwd",
|
||||
})
|
||||
messages = append(messages, Message{
|
||||
Role: ASSISTANT.String(),
|
||||
Content: "/home/user",
|
||||
})
|
||||
for _, history := range histories {
|
||||
messages = append(messages, history)
|
||||
}
|
||||
case tracer.HTTP:
|
||||
messages = append(messages, Message{
|
||||
Role: SYSTEM.String(),
|
||||
Content: systemPromptVirtualizeHTTPServer,
|
||||
})
|
||||
messages = append(messages, Message{
|
||||
Role: USER.String(),
|
||||
Content: "GET /index.html",
|
||||
})
|
||||
messages = append(messages, Message{
|
||||
Role: ASSISTANT.String(),
|
||||
Content: "<html><body>Hello, World!</body></html>",
|
||||
})
|
||||
default:
|
||||
return nil, errors.New("no prompt for protocol selected")
|
||||
}
|
||||
messages = append(messages, Message{
|
||||
Role: USER.String(),
|
||||
Content: command,
|
||||
})
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (llmHoneypot *LLMHoneypot) openAICaller(messages []Message) (string, error) {
|
||||
var err error
|
||||
|
||||
requestJson, err := json.Marshal(Request{
|
||||
Model: "gpt-4o",
|
||||
Messages: messages,
|
||||
Stream: false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if llmHoneypot.OpenAIKey == "" {
|
||||
return "", errors.New("openAIKey is empty")
|
||||
}
|
||||
|
||||
if llmHoneypot.Host == "" {
|
||||
llmHoneypot.Host = openAIGPTEndpoint
|
||||
}
|
||||
|
||||
log.Debug(string(requestJson))
|
||||
response, err := llmHoneypot.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(requestJson).
|
||||
SetAuthToken(llmHoneypot.OpenAIKey).
|
||||
SetResult(&Response{}).
|
||||
Post(llmHoneypot.Host)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Debug(response)
|
||||
if len(response.Result().(*Response).Choices) == 0 {
|
||||
return "", errors.New("no choices")
|
||||
}
|
||||
|
||||
return response.Result().(*Response).Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
func (llmHoneypot *LLMHoneypot) ollamaCaller(messages []Message) (string, error) {
|
||||
var err error
|
||||
|
||||
requestJson, err := json.Marshal(Request{
|
||||
Model: "llama3",
|
||||
Messages: messages,
|
||||
Stream: false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if llmHoneypot.Host == "" {
|
||||
llmHoneypot.Host = ollamaEndpoint
|
||||
}
|
||||
|
||||
log.Debug(string(requestJson))
|
||||
response, err := llmHoneypot.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(requestJson).
|
||||
SetResult(&Response{}).
|
||||
Post(llmHoneypot.Host)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Debug(response)
|
||||
|
||||
return response.Result().(*Response).Message.Content, nil
|
||||
}
|
||||
|
||||
func (llmHoneypot *LLMHoneypot) ExecuteModel(command string) (string, error) {
|
||||
var err error
|
||||
|
||||
prompt, err := buildPrompt(llmHoneypot.Histories, llmHoneypot.Protocol, command)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch llmHoneypot.Model {
|
||||
case LLAMA3:
|
||||
return llmHoneypot.ollamaCaller(prompt)
|
||||
case GPT4O:
|
||||
return llmHoneypot.openAICaller(prompt)
|
||||
default:
|
||||
return "", errors.New("no model selected")
|
||||
}
|
||||
}
|
300
plugins/llm-integration_test.go
Normal file
300
plugins/llm-integration_test.go
Normal file
@ -0,0 +1,300 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const SystemPromptLen = 4
|
||||
|
||||
func TestBuildPromptEmptyHistory(t *testing.T) {
|
||||
//Given
|
||||
var histories []Message
|
||||
command := "pwd"
|
||||
|
||||
//When
|
||||
prompt, err := buildPrompt(histories, tracer.SSH, command)
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, SystemPromptLen, len(prompt))
|
||||
}
|
||||
|
||||
func TestBuildPromptWithHistory(t *testing.T) {
|
||||
//Given
|
||||
var histories = []Message{
|
||||
{
|
||||
Role: "cat hello.txt",
|
||||
Content: "world",
|
||||
},
|
||||
}
|
||||
|
||||
command := "pwd"
|
||||
|
||||
//When
|
||||
prompt, err := buildPrompt(histories, tracer.SSH, command)
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, SystemPromptLen+1, len(prompt))
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelFailValidation(t *testing.T) {
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "",
|
||||
Protocol: tracer.SSH,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
|
||||
_, err := openAIGPTVirtualTerminal.ExecuteModel("test")
|
||||
|
||||
assert.Equal(t, "openAIKey is empty", err.Error())
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelFailValidationStrategyType(t *testing.T) {
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "",
|
||||
Protocol: tracer.TCP,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
|
||||
_, err := openAIGPTVirtualTerminal.ExecuteModel("test")
|
||||
|
||||
assert.Equal(t, "no prompt for protocol selected", err.Error())
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelFailValidationModelType(t *testing.T) {
|
||||
// Given
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
Protocol: tracer.SSH,
|
||||
Model: 5,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
|
||||
//When
|
||||
_, err := openAIGPTVirtualTerminal.ExecuteModel("ls")
|
||||
|
||||
//Then
|
||||
assert.Errorf(t, err, "no model selected")
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelSSHWithResultsOpenAI(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", openAIGPTEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &Response{
|
||||
Choices: []Choice{
|
||||
{
|
||||
Message: Message{
|
||||
Role: SYSTEM.String(),
|
||||
Content: "prova.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "sdjdnklfjndslkjanfk",
|
||||
Protocol: tracer.SSH,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
openAIGPTVirtualTerminal.client = client
|
||||
|
||||
//When
|
||||
str, err := openAIGPTVirtualTerminal.ExecuteModel("ls")
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "prova.txt", str)
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelSSHWithResultsLLama(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", ollamaEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &Response{
|
||||
Message: Message{
|
||||
Role: SYSTEM.String(),
|
||||
Content: "prova.txt",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
Protocol: tracer.SSH,
|
||||
Model: LLAMA3,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
openAIGPTVirtualTerminal.client = client
|
||||
|
||||
//When
|
||||
str, err := openAIGPTVirtualTerminal.ExecuteModel("ls")
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "prova.txt", str)
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelSSHWithoutResults(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", openAIGPTEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &Response{
|
||||
Choices: []Choice{},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "sdjdnklfjndslkjanfk",
|
||||
Protocol: tracer.SSH,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
openAIGPTVirtualTerminal.client = client
|
||||
|
||||
//When
|
||||
_, err := openAIGPTVirtualTerminal.ExecuteModel("ls")
|
||||
|
||||
//Then
|
||||
assert.Equal(t, "no choices", err.Error())
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelHTTPWithResults(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", openAIGPTEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &Response{
|
||||
Choices: []Choice{
|
||||
{
|
||||
Message: Message{
|
||||
Role: SYSTEM.String(),
|
||||
Content: "[default]\nregion = us-west-2\noutput = json",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "sdjdnklfjndslkjanfk",
|
||||
Protocol: tracer.HTTP,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
openAIGPTVirtualTerminal.client = client
|
||||
|
||||
//When
|
||||
str, err := openAIGPTVirtualTerminal.ExecuteModel("GET /.aws/credentials")
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "[default]\nregion = us-west-2\noutput = json", str)
|
||||
}
|
||||
|
||||
func TestBuildExecuteModelHTTPWithoutResults(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", openAIGPTEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &Response{
|
||||
Choices: []Choice{},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
llmHoneypot := LLMHoneypot{
|
||||
Histories: make([]Message, 0),
|
||||
OpenAIKey: "sdjdnklfjndslkjanfk",
|
||||
Protocol: tracer.HTTP,
|
||||
Model: GPT4O,
|
||||
}
|
||||
|
||||
openAIGPTVirtualTerminal := InitLLMHoneypot(llmHoneypot)
|
||||
openAIGPTVirtualTerminal.client = client
|
||||
|
||||
//When
|
||||
_, err := openAIGPTVirtualTerminal.ExecuteModel("GET /.aws/credentials")
|
||||
|
||||
//Then
|
||||
assert.Equal(t, "no choices", err.Error())
|
||||
}
|
||||
|
||||
func TestFromString(t *testing.T) {
|
||||
model, err := FromStringToLLMModel("llama3")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, LLAMA3, model)
|
||||
|
||||
model, err = FromStringToLLMModel("gpt4-o")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, GPT4O, model)
|
||||
|
||||
model, err = FromStringToLLMModel("beelzebub-model")
|
||||
assert.Errorf(t, err, "model beelzebub-model not found")
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Reference: https://www.engraved.blog/building-a-virtual-machine-inside/
|
||||
promptVirtualizeLinuxTerminal = "I want you to act as a Linux terminal. I will type commands and you will reply with what the terminal should show. I want you to only reply with the terminal output inside one unique code block, and nothing else. Do no write explanations. Do not type commands unless I instruct you to do so.\n\nA:pwd\n\nQ:/home/user\n\n"
|
||||
ChatGPTPluginName = "OpenAIGPTLinuxTerminal"
|
||||
openAIGPTEndpoint = "https://api.openai.com/v1/completions"
|
||||
)
|
||||
|
||||
type History struct {
|
||||
Input, Output string
|
||||
}
|
||||
|
||||
type OpenAIGPTVirtualTerminal struct {
|
||||
Histories []History
|
||||
OpenAPIChatGPTSecretKey string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (openAIGPTVirtualTerminal *OpenAIGPTVirtualTerminal) InjectDependency() {
|
||||
if openAIGPTVirtualTerminal.client == nil {
|
||||
openAIGPTVirtualTerminal.client = resty.New()
|
||||
}
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
Text string `json:"text"`
|
||||
Index int `json:"index"`
|
||||
Logprobs interface{} `json:"logprobs"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
type gptResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type gptRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature int `json:"temperature"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
TopP int `json:"top_p"`
|
||||
FrequencyPenalty int `json:"frequency_penalty"`
|
||||
PresencePenalty int `json:"presence_penalty"`
|
||||
Stop []string `json:"stop"`
|
||||
}
|
||||
|
||||
func buildPrompt(histories []History, command string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(promptVirtualizeLinuxTerminal)
|
||||
|
||||
for _, history := range histories {
|
||||
sb.WriteString(fmt.Sprintf("A:%s\n\nQ:%s\n\n", history.Input, history.Output))
|
||||
}
|
||||
// Append command to evaluate
|
||||
sb.WriteString(fmt.Sprintf("A:%s\n\nQ:", command))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (openAIGPTVirtualTerminal *OpenAIGPTVirtualTerminal) GetCompletions(command string) (string, error) {
|
||||
requestJson, err := json.Marshal(gptRequest{
|
||||
Model: "text-davinci-003",
|
||||
Prompt: buildPrompt(openAIGPTVirtualTerminal.Histories, command),
|
||||
Temperature: 0,
|
||||
MaxTokens: 100,
|
||||
TopP: 1,
|
||||
FrequencyPenalty: 0,
|
||||
PresencePenalty: 0,
|
||||
Stop: []string{"\n"},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if openAIGPTVirtualTerminal.OpenAPIChatGPTSecretKey == "" {
|
||||
return "", errors.New("OpenAPIChatGPTSecretKey is empty")
|
||||
}
|
||||
|
||||
response, err := openAIGPTVirtualTerminal.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(requestJson).
|
||||
SetAuthToken(openAIGPTVirtualTerminal.OpenAPIChatGPTSecretKey).
|
||||
SetResult(&gptResponse{}).
|
||||
Post(openAIGPTEndpoint)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Debug(response)
|
||||
if len(response.Result().(*gptResponse).Choices) == 0 {
|
||||
return "", errors.New("no choices")
|
||||
}
|
||||
|
||||
return response.Result().(*gptResponse).Choices[0].Text, nil
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildPromptEmptyHistory(t *testing.T) {
|
||||
//Given
|
||||
var histories []History
|
||||
command := "pwd"
|
||||
|
||||
//When
|
||||
prompt := buildPrompt(histories, command)
|
||||
|
||||
//Then
|
||||
assert.Equal(t,
|
||||
"I want you to act as a Linux terminal. I will type commands and you will reply with what the terminal should show. I want you to only reply with the terminal output inside one unique code block, and nothing else. Do no write explanations. Do not type commands unless I instruct you to do so.\n\nA:pwd\n\nQ:/home/user\n\nA:pwd\n\nQ:",
|
||||
prompt)
|
||||
}
|
||||
|
||||
func TestBuildPromptWithHistory(t *testing.T) {
|
||||
//Given
|
||||
var histories = []History{
|
||||
{
|
||||
Input: "cat hello.txt",
|
||||
Output: "world",
|
||||
},
|
||||
{
|
||||
Input: "echo 1234",
|
||||
Output: "1234",
|
||||
},
|
||||
}
|
||||
|
||||
command := "pwd"
|
||||
|
||||
//When
|
||||
prompt := buildPrompt(histories, command)
|
||||
|
||||
//Then
|
||||
assert.Equal(t,
|
||||
"I want you to act as a Linux terminal. I will type commands and you will reply with what the terminal should show. I want you to only reply with the terminal output inside one unique code block, and nothing else. Do no write explanations. Do not type commands unless I instruct you to do so.\n\nA:pwd\n\nQ:/home/user\n\nA:cat hello.txt\n\nQ:world\n\nA:echo 1234\n\nQ:1234\n\nA:pwd\n\nQ:",
|
||||
prompt)
|
||||
}
|
||||
|
||||
func TestBuildGetCompletions(t *testing.T) {
|
||||
client := resty.New()
|
||||
httpmock.ActivateNonDefault(client.GetClient())
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
// Given
|
||||
httpmock.RegisterResponder("POST", openAIGPTEndpoint,
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := httpmock.NewJsonResponse(200, &gptResponse{
|
||||
Choices: []Choice{
|
||||
{
|
||||
Text: "prova.txt",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return httpmock.NewStringResponse(500, ""), nil
|
||||
}
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
openAIGPTVirtualTerminal := OpenAIGPTVirtualTerminal{
|
||||
OpenAPIChatGPTSecretKey: "sdjdnklfjndslkjanfk",
|
||||
client: client,
|
||||
}
|
||||
|
||||
//When
|
||||
str, err := openAIGPTVirtualTerminal.GetCompletions("ls")
|
||||
|
||||
//Then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "prova.txt", str)
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
// Package protocols is responsible for managing the different protocols
|
||||
package protocols
|
||||
|
||||
import (
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
)
|
||||
|
||||
// ServiceStrategy is the common interface that each protocol honeypot implements
|
||||
type ServiceStrategy interface {
|
||||
Init(beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration, tracer tracer.Tracer) error
|
||||
}
|
||||
@ -14,10 +16,11 @@ type ProtocolManager struct {
|
||||
tracer tracer.Tracer
|
||||
}
|
||||
|
||||
func InitProtocolManager(tracerStrategy tracer.Strategy, strategy ServiceStrategy) *ProtocolManager {
|
||||
// InitProtocolManager is the method that initializes the protocol manager, receving the concrete tracer and the concrete service
|
||||
func InitProtocolManager(tracerStrategy tracer.Strategy, serviceStrategy ServiceStrategy) *ProtocolManager {
|
||||
return &ProtocolManager{
|
||||
tracer: tracer.Init(tracerStrategy),
|
||||
strategy: strategy,
|
||||
tracer: tracer.GetInstance(tracerStrategy),
|
||||
strategy: serviceStrategy,
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +28,7 @@ func (pm *ProtocolManager) SetProtocolStrategy(strategy ServiceStrategy) {
|
||||
pm.strategy = strategy
|
||||
}
|
||||
|
||||
// InitService is the method that initializes the honeypot
|
||||
func (pm *ProtocolManager) InitService(beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration) error {
|
||||
return pm.strategy.Init(beelzebubServiceConfiguration, pm.tracer)
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package protocols
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
@ -2,9 +2,11 @@ package strategies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -31,8 +33,40 @@ func (httpStrategy HTTPStrategy) Init(beelzebubServiceConfiguration parser.Beelz
|
||||
}
|
||||
|
||||
if matched {
|
||||
responseHTTPBody := command.Handler
|
||||
|
||||
if command.Plugin == plugins.LLMPluginName {
|
||||
|
||||
llmModel, err := plugins.FromStringToLLMModel(beelzebubServiceConfiguration.Plugin.LLMModel)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error fromString: %s", err.Error())
|
||||
responseHTTPBody = "404 Not Found!"
|
||||
}
|
||||
|
||||
llmHoneypot := plugins.LLMHoneypot{
|
||||
Histories: make([]plugins.Message, 0),
|
||||
OpenAIKey: beelzebubServiceConfiguration.Plugin.OpenAISecretKey,
|
||||
Protocol: tracer.HTTP,
|
||||
Host: beelzebubServiceConfiguration.Plugin.Host,
|
||||
Model: llmModel,
|
||||
}
|
||||
|
||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||
|
||||
command := fmt.Sprintf("%s %s", request.Method, request.RequestURI)
|
||||
|
||||
if completions, err := llmHoneypotInstance.ExecuteModel(command); err != nil {
|
||||
log.Errorf("Error ExecuteModel: %s, %s", command, err.Error())
|
||||
responseHTTPBody = "404 Not Found!"
|
||||
} else {
|
||||
responseHTTPBody = completions
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setResponseHeaders(responseWriter, command.Headers, command.StatusCode)
|
||||
fmt.Fprintf(responseWriter, command.Handler)
|
||||
fmt.Fprintf(responseWriter, responseHTTPBody)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -58,6 +92,8 @@ func traceRequest(request *http.Request, tr tracer.Tracer, HoneypotDescription s
|
||||
if err == nil {
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
host, port, _ := net.SplitHostPort(request.RemoteAddr)
|
||||
|
||||
tr.TraceEvent(tracer.Event{
|
||||
Msg: "HTTP New request",
|
||||
RequestURI: request.RequestURI,
|
||||
@ -70,6 +106,8 @@ func traceRequest(request *http.Request, tr tracer.Tracer, HoneypotDescription s
|
||||
Headers: mapHeaderToString(request.Header),
|
||||
Status: tracer.Stateless.String(),
|
||||
RemoteAddr: request.RemoteAddr,
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
ID: uuid.New().String(),
|
||||
Description: HoneypotDescription,
|
||||
})
|
||||
|
@ -2,9 +2,10 @@ package strategies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/plugins"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -28,10 +29,14 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
Handler: func(sess ssh.Session) {
|
||||
uuidSession := uuid.New()
|
||||
|
||||
host, port, _ := net.SplitHostPort(sess.RemoteAddr().String())
|
||||
|
||||
tr.TraceEvent(tracer.Event{
|
||||
Msg: "New SSH Session",
|
||||
Protocol: tracer.SSH.String(),
|
||||
RemoteAddr: sess.RemoteAddr().String(),
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
Status: tracer.Start.String(),
|
||||
ID: uuidSession.String(),
|
||||
Environ: strings.Join(sess.Environ(), ","),
|
||||
@ -41,7 +46,7 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
})
|
||||
|
||||
term := terminal.NewTerminal(sess, buildPrompt(sess.User(), beelzebubServiceConfiguration.ServerName))
|
||||
var histories []plugins.History
|
||||
var histories []plugins.Message
|
||||
for {
|
||||
commandInput, err := term.ReadLine()
|
||||
if err != nil {
|
||||
@ -61,23 +66,41 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
if matched {
|
||||
commandOutput := command.Handler
|
||||
|
||||
if command.Plugin == plugins.ChatGPTPluginName {
|
||||
openAIGPTVirtualTerminal := plugins.OpenAIGPTVirtualTerminal{Histories: histories, OpenAPIChatGPTSecretKey: beelzebubServiceConfiguration.Plugin.OpenAPIChatGPTSecretKey}
|
||||
openAIGPTVirtualTerminal.InjectDependency()
|
||||
if command.Plugin == plugins.LLMPluginName {
|
||||
|
||||
if commandOutput, err = openAIGPTVirtualTerminal.GetCompletions(commandInput); err != nil {
|
||||
log.Errorf("Error GetCompletions: %s, %s", commandInput, err.Error())
|
||||
llmModel, err := plugins.FromStringToLLMModel(beelzebubServiceConfiguration.Plugin.LLMModel)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error fromString: %s", err.Error())
|
||||
commandOutput = "command not found"
|
||||
}
|
||||
|
||||
llmHoneypot := plugins.LLMHoneypot{
|
||||
Histories: histories,
|
||||
OpenAIKey: beelzebubServiceConfiguration.Plugin.OpenAISecretKey,
|
||||
Protocol: tracer.SSH,
|
||||
Host: beelzebubServiceConfiguration.Plugin.Host,
|
||||
Model: llmModel,
|
||||
}
|
||||
|
||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||
|
||||
if commandOutput, err = llmHoneypotInstance.ExecuteModel(commandInput); err != nil {
|
||||
log.Errorf("Error ExecuteModel: %s, %s", commandInput, err.Error())
|
||||
commandOutput = "command not found"
|
||||
}
|
||||
}
|
||||
|
||||
histories = append(histories, plugins.History{Input: commandInput, Output: commandOutput})
|
||||
histories = append(histories, plugins.Message{Role: plugins.USER.String(), Content: commandInput})
|
||||
histories = append(histories, plugins.Message{Role: plugins.ASSISTANT.String(), Content: commandOutput})
|
||||
|
||||
term.Write(append([]byte(commandOutput), '\n'))
|
||||
|
||||
tr.TraceEvent(tracer.Event{
|
||||
Msg: "New SSH Terminal Session",
|
||||
RemoteAddr: sess.RemoteAddr().String(),
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
Status: tracer.Interaction.String(),
|
||||
Command: commandInput,
|
||||
CommandOutput: commandOutput,
|
||||
@ -96,6 +119,8 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
})
|
||||
},
|
||||
PasswordHandler: func(ctx ssh.Context, password string) bool {
|
||||
host, port, _ := net.SplitHostPort(ctx.RemoteAddr().String())
|
||||
|
||||
tr.TraceEvent(tracer.Event{
|
||||
Msg: "New SSH attempt",
|
||||
Protocol: tracer.SSH.String(),
|
||||
@ -104,6 +129,8 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
Password: password,
|
||||
Client: ctx.ClientVersion(),
|
||||
RemoteAddr: ctx.RemoteAddr().String(),
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
ID: uuid.New().String(),
|
||||
Description: beelzebubServiceConfiguration.Description,
|
||||
})
|
||||
@ -124,7 +151,7 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
log.WithFields(log.Fields{
|
||||
"port": beelzebubServiceConfiguration.Address,
|
||||
"commands": len(beelzebubServiceConfiguration.Commands),
|
||||
}).Infof("Init service %s", beelzebubServiceConfiguration.Protocol)
|
||||
}).Infof("GetInstance service %s", beelzebubServiceConfiguration.Protocol)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ package strategies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mariocandela/beelzebub/parser"
|
||||
"github.com/mariocandela/beelzebub/tracer"
|
||||
"github.com/mariocandela/beelzebub/v3/parser"
|
||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@ -35,12 +35,16 @@ func (tcpStrategy *TCPStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
||||
command = string(buffer[:n])
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
|
||||
tr.TraceEvent(tracer.Event{
|
||||
Msg: "New TCP attempt",
|
||||
Protocol: tracer.TCP.String(),
|
||||
Command: command,
|
||||
Status: tracer.Stateless.String(),
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
SourceIp: host,
|
||||
SourcePort: port,
|
||||
ID: uuid.New().String(),
|
||||
Description: beelzebubServiceConfiguration.Description,
|
||||
})
|
||||
|
119
tracer/tracer.go
119
tracer/tracer.go
@ -1,13 +1,16 @@
|
||||
// Package tracer is responsible for tracing the events that occur in the honeypots
|
||||
package tracer
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Workers is the number of workers that will
|
||||
const Workers = 5
|
||||
|
||||
type Event struct {
|
||||
@ -31,6 +34,8 @@ type Event struct {
|
||||
HTTPMethod string
|
||||
RequestURI string
|
||||
Description string
|
||||
SourceIp string
|
||||
SourcePort string
|
||||
}
|
||||
|
||||
type (
|
||||
@ -44,8 +49,8 @@ const (
|
||||
TCP
|
||||
)
|
||||
|
||||
func (status Protocol) String() string {
|
||||
return [...]string{"HTTP", "SSH", "TCP"}[status]
|
||||
func (protocol Protocol) String() string {
|
||||
return [...]string{"HTTP", "SSH", "TCP"}[protocol]
|
||||
}
|
||||
|
||||
const (
|
||||
@ -66,49 +71,60 @@ type Tracer interface {
|
||||
}
|
||||
|
||||
type tracer struct {
|
||||
strategy Strategy
|
||||
eventsChan chan Event
|
||||
strategy Strategy
|
||||
eventsChan chan Event
|
||||
eventsTotal prometheus.Counter
|
||||
eventsSSHTotal prometheus.Counter
|
||||
eventsTCPTotal prometheus.Counter
|
||||
eventsHTTPTotal prometheus.Counter
|
||||
}
|
||||
|
||||
var (
|
||||
eventsTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "events_total",
|
||||
Help: "The total number of events",
|
||||
})
|
||||
eventsSSHTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "ssh_events_total",
|
||||
Help: "The total number of SSH events",
|
||||
})
|
||||
eventsTCPTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "tcp_events_total",
|
||||
Help: "The total number of TCP events",
|
||||
})
|
||||
eventsHTTPTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "http_events_total",
|
||||
Help: "The total number of HTTP events",
|
||||
})
|
||||
)
|
||||
var lock = &sync.Mutex{}
|
||||
var singleton *tracer
|
||||
|
||||
func Init(strategy Strategy) *tracer {
|
||||
tracer := &tracer{
|
||||
strategy: strategy,
|
||||
eventsChan: make(chan Event, Workers),
|
||||
}
|
||||
|
||||
for i := 0; i < Workers; i++ {
|
||||
go func(i int) {
|
||||
log.Debug("Init trace worker: ", i)
|
||||
for event := range tracer.eventsChan {
|
||||
tracer.strategy(event)
|
||||
func GetInstance(defaultStrategy Strategy) *tracer {
|
||||
if singleton == nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
// This is to prevent expensive lock operations every time the GetInstance method is called
|
||||
if singleton == nil {
|
||||
singleton = &tracer{
|
||||
strategy: defaultStrategy,
|
||||
eventsChan: make(chan Event, Workers),
|
||||
eventsTotal: promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "events_total",
|
||||
Help: "The total number of events",
|
||||
}),
|
||||
eventsSSHTotal: promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "ssh_events_total",
|
||||
Help: "The total number of SSH events",
|
||||
}),
|
||||
eventsTCPTotal: promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "tcp_events_total",
|
||||
Help: "The total number of TCP events",
|
||||
}),
|
||||
eventsHTTPTotal: promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "beelzebub",
|
||||
Name: "http_events_total",
|
||||
Help: "The total number of HTTP events",
|
||||
}),
|
||||
}
|
||||
}(i)
|
||||
|
||||
for i := 0; i < Workers; i++ {
|
||||
go func(i int) {
|
||||
log.Debug("Trace worker: ", i)
|
||||
for event := range singleton.eventsChan {
|
||||
singleton.strategy(event)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tracer
|
||||
return singleton
|
||||
}
|
||||
|
||||
func (tracer *tracer) setStrategy(strategy Strategy) {
|
||||
@ -120,14 +136,17 @@ func (tracer *tracer) TraceEvent(event Event) {
|
||||
|
||||
tracer.eventsChan <- event
|
||||
|
||||
eventsTotal.Inc()
|
||||
|
||||
switch event.Protocol {
|
||||
case HTTP.String():
|
||||
eventsHTTPTotal.Inc()
|
||||
case SSH.String():
|
||||
eventsSSHTotal.Inc()
|
||||
case TCP.String():
|
||||
eventsTCPTotal.Inc()
|
||||
}
|
||||
tracer.updatePrometheusCounters(event.Protocol)
|
||||
}
|
||||
|
||||
func (tracer *tracer) updatePrometheusCounters(protocol string) {
|
||||
switch protocol {
|
||||
case HTTP.String():
|
||||
tracer.eventsHTTPTotal.Inc()
|
||||
case SSH.String():
|
||||
tracer.eventsSSHTotal.Inc()
|
||||
case TCP.String():
|
||||
tracer.eventsTCPTotal.Inc()
|
||||
}
|
||||
tracer.eventsTotal.Inc()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tracer
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -10,7 +11,7 @@ import (
|
||||
func TestInit(t *testing.T) {
|
||||
mockStrategy := func(event Event) {}
|
||||
|
||||
tracer := Init(mockStrategy)
|
||||
tracer := GetInstance(mockStrategy)
|
||||
|
||||
assert.NotNil(t, tracer.strategy)
|
||||
}
|
||||
@ -25,7 +26,9 @@ func TestTraceEvent(t *testing.T) {
|
||||
eventCalled = event
|
||||
}
|
||||
|
||||
tracer := Init(mockStrategy)
|
||||
tracer := GetInstance(mockStrategy)
|
||||
|
||||
tracer.strategy = mockStrategy
|
||||
|
||||
wg.Add(1)
|
||||
tracer.TraceEvent(Event{
|
||||
@ -51,7 +54,7 @@ func TestSetStrategy(t *testing.T) {
|
||||
eventCalled = event
|
||||
}
|
||||
|
||||
tracer := Init(mockStrategy)
|
||||
tracer := GetInstance(mockStrategy)
|
||||
|
||||
tracer.setStrategy(mockStrategy)
|
||||
|
||||
@ -75,3 +78,42 @@ func TestStringStatus(t *testing.T) {
|
||||
assert.Equal(t, Stateless.String(), "Stateless")
|
||||
assert.Equal(t, Interaction.String(), "Interaction")
|
||||
}
|
||||
|
||||
type mockCounter struct {
|
||||
prometheus.Metric
|
||||
prometheus.Collector
|
||||
inc func()
|
||||
add func(float64)
|
||||
}
|
||||
|
||||
var counter = 0
|
||||
|
||||
func (m mockCounter) Inc() {
|
||||
counter += 1
|
||||
}
|
||||
|
||||
func (m mockCounter) Add(f float64) {
|
||||
counter = int(f)
|
||||
}
|
||||
|
||||
func TestUpdatePrometheusCounters(t *testing.T) {
|
||||
mockStrategy := func(event Event) {}
|
||||
|
||||
tracer := &tracer{
|
||||
strategy: mockStrategy,
|
||||
eventsChan: make(chan Event, Workers),
|
||||
eventsTotal: mockCounter{},
|
||||
eventsSSHTotal: mockCounter{},
|
||||
eventsTCPTotal: mockCounter{},
|
||||
eventsHTTPTotal: mockCounter{},
|
||||
}
|
||||
|
||||
tracer.updatePrometheusCounters(SSH.String())
|
||||
assert.Equal(t, 2, counter)
|
||||
|
||||
tracer.updatePrometheusCounters(HTTP.String())
|
||||
assert.Equal(t, 4, counter)
|
||||
|
||||
tracer.updatePrometheusCounters(TCP.String())
|
||||
assert.Equal(t, 6, counter)
|
||||
}
|
||||
|
Reference in New Issue
Block a user