diff --git a/.gitignore b/.gitignore index a1e7920..6b976d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,6 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - # Test binary, built with `go test -c` *.test -# Production binary folder -bin/ - # Output of the go coverage tool, specifically when used with LiteIDE *.out @@ -22,17 +12,14 @@ bin/ .vscode/ .DS_Store .dccache +.iac-data ## VSCode workspace file workspace*.code-* -# Vendor folder +# Transit folders /vendor/ - -# Plugins -*.so - -# TCPdump -build/tcpdump/ +/statik/ +/bin/ # whatever the rules are, include all the `.md` files !*.md diff --git a/Makefile b/Makefile index ff312bd..67ba800 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SHELL := /bin/bash APPNAME=riotpot DOCKER=build/docker/ PLUGINS_DIR=pkg/plugin -EXCLUDE_PLUGINS= sshd modbusd echod coapd telnetd mqttd httpd +EXCLUDE_PLUGINS= modbusd coapd mqttd # docker cmd below .PHONY: docker-build-doc docker-doc-up up down up-all build build-plugins build-all ui @@ -20,9 +20,9 @@ up-all: riotpot-doc riotpot-up build: - go build -gcflags='all=-N -l' -o ./bin/ ./cmd/riotpot/. + @go build -gcflags='all=-N -l' -o ./bin/ ./cmd/riotpot/. build-plugins: $(PLUGINS_DIR)/* - IFS=' ' read -r -a exclude <<< "${EXCLUDE_PLUGINS}"; \ + @IFS=' ' read -r -a exclude <<< "${EXCLUDE_PLUGINS}"; \ for folder in $^ ; do \ result=$${folder%%+(/)}; \ result=$${result##*/}; \ diff --git a/build/docker/docker-compose.yaml b/build/docker/docker-compose.yaml index d324305..1e704e4 100644 --- a/build/docker/docker-compose.yaml +++ b/build/docker/docker-compose.yaml @@ -26,7 +26,7 @@ services: container_name: tcpdump network_mode: "host" volumes: - - ../tcpdump:/tcpdump + - ../../tcpdump:/tcpdump # Run tcdump in autorotating mode, with gzip compression # The files will be rotated every 24h or 500MB and named # after the timestamp when the file is created. @@ -52,7 +52,7 @@ services: # Ports under 60 might see errors when unquoted # https://stackoverflow.com/questions/58810789/quotes-on-docker-compose-yml-ports-make-any-difference - "7:7" - # - "22:22" + - "22:22" - "23:23" - "80:80" - "502:502" diff --git a/cmd/riotpot/flags.go b/cmd/riotpot/flags.go index 3ab19f2..edcab82 100644 --- a/cmd/riotpot/flags.go +++ b/cmd/riotpot/flags.go @@ -18,7 +18,7 @@ import ( "github.com/riotpot/api/service" "github.com/riotpot/internal/globals" "github.com/riotpot/internal/logger" - "github.com/riotpot/pkg" + "github.com/riotpot/internal/plugins" "github.com/rs/zerolog" _ "github.com/riotpot/statik" @@ -39,7 +39,7 @@ var ( var ( debug = flag.Bool("debug", true, "Set log level to debug") runApi = flag.Bool("api", true, "Whether to start the API") - plugins = flag.Bool("plugins", true, "Whether to load the low-interaction honeypot plugins") + loadPlugins = flag.Bool("plugins", true, "Whether to load the low-interaction honeypot plugins") allowedHosts = flag.String("whitelist", "http://127.0.0.1,http://localhost:3000", "List of allowed hosts to contact the API") ) @@ -85,8 +85,8 @@ func ParseFlags() { } // Load the plugins - if *plugins { - pkg.LoadPlugins() + if *loadPlugins { + plugins.LoadPlugins() } // Starts the API diff --git a/configs/configuration.yml b/configs/configuration.yml deleted file mode 100644 index 097ee66..0000000 --- a/configs/configuration.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# RiotPot configuration file. -# This file can be used as a template for further implementations and as a record of -# documentation for internal structure when in doubt on usage. -riotpot: - # The name of the services is the name of the folder in which the plugin - # is stored, inside the `pkg/` folder. - # Example: - # * if the plugin is stored in: `pkg/telnetd` - # * then place: `- telnetd` - - # Contains a list of available services in the application. - # This gives the user the ability to navigate or load - # just the emulators that appear in this list, wether or not - # the emulator plugin appears in the binary. - # - # Add here the emulator plugins you have included on the binary. - emulators: - - httpd - - echod - - sshd - - telnetd - - mqttd - - coapd - - modbusd - - # `start` contains a list of services desired to be run - # on-start variable used in Riotpot core on runtime only! - # For all the services leave it empty - start: [] diff --git a/configs/plugin.go b/configs/plugin.template similarity index 100% rename from configs/plugin.go rename to configs/plugin.template diff --git a/internal/plugins/keys.go b/internal/plugins/keys.go new file mode 100644 index 0000000..bf0f86b --- /dev/null +++ b/internal/plugins/keys.go @@ -0,0 +1,91 @@ +package plugins + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + + "github.com/riotpot/internal/logger" +) + +type ( + KeyType string + KeySize int +) + +const ( + Public KeyType = "public" + Private KeyType = "private" + + InsecureKey KeySize = 1024 + LiteKey KeySize = 2048 + DefaultKey KeySize = 4096 +) + +type CKey interface { + Generate() []byte + GetPEM() []byte + SetPEM(pem []byte) +} + +type AbstractKey struct { + CKey + pem []byte +} + +func (k *AbstractKey) GetPEM() []byte { + return k.pem +} + +func (k *AbstractKey) SetPEM(pem []byte) { + k.pem = pem +} + +type PrivateKey struct { + key AbstractKey + priv *rsa.PrivateKey +} + +func (k *PrivateKey) GetPEM() []byte { + return k.key.GetPEM() +} + +func (k *PrivateKey) SetPEM(pem []byte) { + k.key.SetPEM(pem) +} + +func (k *PrivateKey) SetKey(key *rsa.PrivateKey) { + k.priv = key +} + +// Function to Generate and store a private RSA key and PEM +func (k *PrivateKey) Generate(size KeySize) (cert []byte) { + reader := rand.Reader + priv, err := rsa.GenerateKey(reader, int(size)) + if err != nil { + logger.Log.Fatal().Err(err) + } + + err = priv.Validate() + if err != nil { + logger.Log.Fatal().Err(err) + } + + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + } + cert = pem.EncodeToMemory(block) + + k.SetKey(priv) + k.SetPEM(cert) + return +} + +func NewPrivateKey(size KeySize) *PrivateKey { + k := &PrivateKey{} + + k.Generate(size) + return k +} diff --git a/pkg/plugins.go b/internal/plugins/plugins.go similarity index 89% rename from pkg/plugins.go rename to internal/plugins/plugins.go index f2d59c0..9586808 100644 --- a/pkg/plugins.go +++ b/internal/plugins/plugins.go @@ -1,4 +1,4 @@ -package pkg +package plugins import ( "fmt" @@ -6,9 +6,8 @@ import ( "plugin" "github.com/riotpot/internal/logger" - proxies "github.com/riotpot/internal/proxy" + "github.com/riotpot/internal/proxy" "github.com/riotpot/internal/services" - "github.com/riotpot/tools/errors" ) var ( @@ -24,19 +23,19 @@ func getServicePlugin(path string) services.Service { // Open the plugin within the path pg, err := plugin.Open(path) - errors.Raise(err) + logger.Log.Fatal().Err(err) // check the name of the function that exports the service // The plugin *Must* contain a variable called `Plugin`. s, err := pg.Lookup("Plugin") - errors.Raise(err) + logger.Log.Fatal().Err(err) // log the name of the plugin being loaded fmt.Printf("Loading plugin: %s...\n", *s.(*string)) // check if the reference symbol exists in the plugin rf, err := pg.Lookup(*s.(*string)) - errors.Raise(err) + logger.Log.Error().Err(err) // Load the service in a variable as the interface Service. newservice := rf.(func() services.Service)() @@ -88,13 +87,13 @@ func LoadPlugins() (errors []error) { // Create proxies for each of the started plugins for _, service := range plugins { - proxy, err := proxies.Proxies.CreateProxy(service.GetNetwork(), service.GetPort()-pluginOffset) + px, err := proxy.Proxies.CreateProxy(service.GetNetwork(), service.GetPort()-pluginOffset) if err != nil { logger.Log.Error().Err(err) } // Add the service to the proxy - proxy.SetService(service) + px.SetService(service) } return diff --git a/pkg/plugin/coapd/coapd.go b/pkg/plugin/coapd/coapd.go index 6115e15..cda65ab 100644 --- a/pkg/plugin/coapd/coapd.go +++ b/pkg/plugin/coapd/coapd.go @@ -66,8 +66,6 @@ func (c *Coap) Run() (err error) { // This will cause all the requests to go through this function. r.DefaultHandleFunc(c.observeHandler) - lr.Log.Info().Msgf("Service %s started listenning for connections in port %d", c.GetName(), c.GetPort()) - // Run the server listening on the given port and using the defined // lvl4 layer protocol. err = coap.ListenAndServe(c.GetNetwork().String(), fmt.Sprintf(":%d", c.GetPort()), r) diff --git a/pkg/plugin/coapd/profiles.go b/pkg/plugin/coapd/profiles.go index 7288c8b..7b84f81 100644 --- a/pkg/plugin/coapd/profiles.go +++ b/pkg/plugin/coapd/profiles.go @@ -14,7 +14,7 @@ import ( "time" "github.com/google/uuid" - "github.com/riotpot/tools/errors" + "github.com/riotpot/internal/logger" "gopkg.in/yaml.v3" ) @@ -33,9 +33,9 @@ type Profile struct { func (p *Profile) Load(path string) { data, err := os.ReadFile(path) - errors.Raise(err) + logger.Log.Error().Err(err) err = yaml.Unmarshal(data, &p) - errors.Raise(err) + logger.Log.Error().Err(err) } // Method that provides a getter for topics and anso creates the topic diff --git a/pkg/plugin/echod/echod.go b/pkg/plugin/echod/echod.go index d50c18b..f751246 100644 --- a/pkg/plugin/echod/echod.go +++ b/pkg/plugin/echod/echod.go @@ -6,10 +6,8 @@ import ( "net" "github.com/riotpot/internal/globals" + "github.com/riotpot/internal/logger" "github.com/riotpot/internal/services" - "github.com/riotpot/tools/errors" - - lr "github.com/riotpot/internal/logger" ) var Plugin string @@ -43,7 +41,7 @@ func (e *Echo) Run() (err error) { // start a service in the `echo` port listener, err := net.Listen(e.GetNetwork().String(), port) - errors.Raise(err) + logger.Log.Error().Err(err) // build a channel stack to receive connections to the service conn := make(chan net.Conn) @@ -59,7 +57,6 @@ func (e *Echo) Run() (err error) { // inspired on https://gist.github.com/paulsmith/775764#file-echo-go func (e *Echo) serve(ch chan net.Conn, listener net.Listener) { // open an infinite loop to receive connections - lr.Log.Info().Msgf("Service %s started listenning for connections in port %d", e.GetName(), e.GetPort()) for { // Accept the client connection client, err := listener.Accept() diff --git a/pkg/plugin/httpd/httpd.go b/pkg/plugin/httpd/httpd.go index e106460..f21b385 100644 --- a/pkg/plugin/httpd/httpd.go +++ b/pkg/plugin/httpd/httpd.go @@ -14,7 +14,7 @@ var Plugin string const ( name = "HTTP" network = globals.TCP - port = 8080 + port = 80 ) func init() { @@ -49,7 +49,6 @@ func (h *Http) Run() (err error) { } func (h *Http) serve(srv *http.Server) { - fmt.Printf("[%s] Started listenning for connections in port %d\n", h.GetName(), h.GetPort()) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { lr.Log.Fatal().Err(err) } diff --git a/pkg/plugin/modbusd/modbusd.go b/pkg/plugin/modbusd/modbusd.go index c1bd856..113e145 100644 --- a/pkg/plugin/modbusd/modbusd.go +++ b/pkg/plugin/modbusd/modbusd.go @@ -6,8 +6,8 @@ import ( "net" "github.com/riotpot/internal/globals" + "github.com/riotpot/internal/logger" "github.com/riotpot/internal/services" - "github.com/riotpot/tools/errors" "github.com/xiegeo/modbusone" ) @@ -54,7 +54,7 @@ func (m *Modbus) Run() (err error) { // start a service in the `echo` port listener, err := net.Listen("tcp", port) - errors.Raise(err) + logger.Log.Error().Err(err) // build a channel stack to receive connections to the service conn := make(chan net.Conn) @@ -67,7 +67,6 @@ func (m *Modbus) Run() (err error) { // inspired on https://gist.github.com/paulsmith/775764#file-echo-go func (m *Modbus) serve(ch chan net.Conn, listener net.Listener) { // open an infinite loop to receive connections - fmt.Printf("[%s] Started listenning for connections in port %d\n", m.GetName(), m.GetPort()) for { // Accept the client connection client, err := listener.Accept() diff --git a/pkg/plugin/mqttd/mqttd.go b/pkg/plugin/mqttd/mqttd.go index 01a4304..b0ff693 100644 --- a/pkg/plugin/mqttd/mqttd.go +++ b/pkg/plugin/mqttd/mqttd.go @@ -7,8 +7,8 @@ import ( "sync" "github.com/riotpot/internal/globals" + "github.com/riotpot/internal/logger" "github.com/riotpot/internal/services" - "github.com/riotpot/tools/errors" ) var Plugin string @@ -44,7 +44,7 @@ func (m *Mqtt) Run() (err error) { // start a service in the `mqtt` port listener, err := net.Listen(m.GetNetwork().String(), port) - errors.Raise(err) + logger.Log.Error().Err(err) // build a channel stack to receive connections to the service conn := make(chan net.Conn) @@ -66,7 +66,6 @@ func (m *Mqtt) serve(ch chan net.Conn, listener net.Listener) { defer m.wg.Done() // open an infinite loop to receive connections - fmt.Printf("[%s] Started listenning for connections in port %d\n", m.GetName(), m.GetPort()) for { // Accept the client connection client, err := listener.Accept() diff --git a/pkg/plugin/sshd/sshd.go b/pkg/plugin/sshd/sshd.go index 1479b60..9d0a355 100644 --- a/pkg/plugin/sshd/sshd.go +++ b/pkg/plugin/sshd/sshd.go @@ -3,14 +3,15 @@ package main import ( "fmt" "io" - "io/ioutil" "net" "sync" "github.com/riotpot/internal/globals" + "github.com/riotpot/internal/logger" + "github.com/riotpot/internal/plugins" "github.com/riotpot/internal/services" "github.com/riotpot/pkg/fake/shell" - "github.com/riotpot/tools/errors" + "github.com/traetox/pty" "golang.org/x/crypto/ssh" ) @@ -31,13 +32,13 @@ func init() { func Sshd() services.Service { mx := services.NewPluginService(name, port, network) - pKey, err := ioutil.ReadFile("riopot_rsa") - errors.Raise(err) + pKey := plugins.NewPrivateKey(plugins.DefaultKey) + pem := pKey.GetPEM() return &SSH{ Service: mx, wg: sync.WaitGroup{}, - privateKey: pKey, + privateKey: pem, } } @@ -49,20 +50,22 @@ type SSH struct { func (s *SSH) Run() (err error) { - // Pre load the configuration for the ssh server + // Preload the configuration for the ssh server config := &ssh.ServerConfig{ PasswordCallback: s.auth, } // Add a private key for the connections - config.AddHostKey(s.PrivateKey()) + priv := s.PrivateKey() + config.AddHostKey(priv) // convert the port number to a string that we can use it in the server - var port = fmt.Sprintf(":%d", s.GetPort()) + port := fmt.Sprintf(":%d", s.GetPort()) - // start a service in the `echo` port listener, err := net.Listen(s.GetNetwork().String(), port) - errors.Raise(err) + if err != nil { + return + } defer listener.Close() // build a channel stack to receive connections to the service @@ -85,43 +88,33 @@ func (s *SSH) auth(c ssh.ConnMetadata, pass []byte) (perms *ssh.Permissions, err func (s *SSH) serve(listener net.Listener, config *ssh.ServerConfig) { // open an infinite loop to receive connections - fmt.Printf("[%s] Started listenning for connections in port %d\n", s.GetName(), s.GetPort()) for { // Accept the client connection client, err := listener.Accept() if err != nil { - return + logger.Log.Error().Err(err) + continue } - defer client.Close() // upgrade the connections to ssh sshConn, chans, reqs, err := ssh.NewServerConn(client, config) if err != nil { - fmt.Printf("Failed to handshake (%s)", err) + logger.Log.Error().Err(err) continue } sshItem := NewSshConn(sshConn) - defer sshConn.Close() - defer sshConn.Conn.Close() - s.wg.Add(1) // Discard all global out-of-band Requests go ssh.DiscardRequests(reqs) // Handle all the channels open by the connection - s.handleChannels(sshItem, chans) - s.wg.Wait() + go s.handleChannels(sshItem, chans) } } func (s *SSH) handleChannels(sshItem SSHConn, chans <-chan ssh.NewChannel) { for conn := range chans { - //TODO: this line crashes the app when the connection is lost!!! - // NOTE: As of [6/21/2022] this line has not been fixed yet. - // Fix it ASAP! - // ☟ ☟ ☟ go s.handleChannel(sshItem, conn) - // ☝ ☝ ☝ } } @@ -136,7 +129,7 @@ func (s *SSH) handleChannel(sshItem SSHConn, channel ssh.NewChannel) { // Accept the channel creation request conn, requests, err := channel.Accept() if err != nil { - fmt.Printf("Could not accept channel (%s)", err) + logger.Log.Error().Err(err) return } @@ -146,23 +139,23 @@ func (s *SSH) handleChannel(sshItem SSHConn, channel ssh.NewChannel) { } // Out-of-band requests handler -// inspired in: https://github.com/traetox/sshForShits/ func (s *SSH) oob(sshItem SSHConn, requests <-chan *ssh.Request, conn ssh.Channel) { + for req := range requests { + switch req.Type { case "shell": - if len(req.Payload) == 0 { - req.Reply(true, nil) - } else { - req.Reply(false, nil) + if len(req.Payload) > 0 { + logger.Log.Error().Msgf("Shell command ignored", req.Payload) } // Give a shell to the client err := s.attachShell(sshItem, conn) if err != nil { - return + logger.Log.Error().Err(err) } + req.Reply(err == nil, nil) case "pty-req": // Responding 'ok' here will let the client // know we have a pty ready for input @@ -172,7 +165,7 @@ func (s *SSH) oob(sshItem SSHConn, requests <-chan *ssh.Request, conn ssh.Channe case "env": continue // no response default: - fmt.Printf("unkown request: %s (reply: %v, data: %x)", req.Type, req.WantReply, req.Payload) + logger.Log.Info().Msgf("Unkown request: %s (reply: %v, data: %x)", req.Type, req.WantReply, req.Payload) } } } @@ -210,7 +203,9 @@ func (s *SSH) attachShell(sshItem SSHConn, conn ssh.Channel) (err error) { func (s *SSH) PrivateKey() (key ssh.Signer) { // Gets the signer from a key key, err := ssh.ParsePrivateKey(s.privateKey) - errors.Raise(err) + if err != nil { + logger.Log.Fatal().Err(err) + } return } diff --git a/pkg/plugin/telnetd/telnetd.go b/pkg/plugin/telnetd/telnetd.go index cc370c9..3b62494 100644 --- a/pkg/plugin/telnetd/telnetd.go +++ b/pkg/plugin/telnetd/telnetd.go @@ -7,10 +7,10 @@ import ( "net" "github.com/riotpot/internal/globals" + "github.com/riotpot/internal/logger" lr "github.com/riotpot/internal/logger" "github.com/riotpot/internal/services" "github.com/riotpot/pkg/fake/shell" - "github.com/riotpot/tools/errors" ) var Plugin string @@ -50,7 +50,7 @@ func (t *Telnet) Run() (err error) { // start a service in the `telnet` port listener, err := net.Listen(t.GetNetwork().String(), port) - errors.Raise(err) + logger.Log.Error().Err(err) // build a channel stack to receive connections to the service conn := make(chan net.Conn) diff --git a/test/pkg/plugins_test.go b/test/internal/plugins/plugins_test.go similarity index 56% rename from test/pkg/plugins_test.go rename to test/internal/plugins/plugins_test.go index 02a2b7d..7021766 100644 --- a/test/pkg/plugins_test.go +++ b/test/internal/plugins/plugins_test.go @@ -4,27 +4,34 @@ import ( "testing" lr "github.com/riotpot/internal/logger" + "github.com/riotpot/internal/plugins" "github.com/riotpot/internal/services" - "github.com/riotpot/pkg" "github.com/stretchr/testify/assert" ) var ( - pluginPath = "../../bin/plugins/*.so" + pluginPath = "../../../bin/plugins/*.so" ) func TestLoadPlugins(t *testing.T) { - plugins, err := pkg.GetPluginServices(pluginPath) + pgs, err := plugins.GetPluginServices(pluginPath) if err != nil { lr.Log.Fatal().Err(err).Msgf("One or more services could not be found") } - plg := plugins[0] + assert.Equal(t, 1, len(pgs)) + + plg := pgs[0] i, ok := plg.(services.PluginService) if !ok { lr.Log.Fatal().Err(err).Msgf("Service is not a plugin") } go i.Run() +} + +func TestNewPrivateKey(t *testing.T) { + key := plugins.NewPrivateKey(plugins.DefaultKey) + pem := key.GetPEM() - assert.Equal(t, 1, len(plugins)) + assert.Equal(t, 1, len(pem)) } diff --git a/tools/environ/syscmd.go b/tools/environ/syscmd.go deleted file mode 100644 index d96da07..0000000 --- a/tools/environ/syscmd.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Package environ provides functions used to interact with the environment -*/ -package environ - -import ( - "log" - "os/exec" - "github.com/riotpot/tools/arrays" -) - -// get the full binary path on host for a given service -func GetPath(service string) (servicePath string) { - servicePath, err := exec.LookPath(service) - - if err != nil { - log.Fatalf("Error in getting service %q with error %q \n", service, err ) - } - return servicePath -} - -// Execute terminal command in async mode i.e. in background -func ExecuteBackgroundCmd(app string, args ...string) { - cmd := exec.Command(app, args...) - err := cmd.Start() - - if err != nil { - log.Fatalf("cmd.Run() for command %q %q failed with %s\n", app, args, err) - } -} - -// Execute command and return the output, if any -func ExecuteCmd(app string, args ...string) (output string) { - cmd := exec.Command(app, args...) - out, err := cmd.CombinedOutput() - - if err != nil { - log.Fatalf("cmd.Run() for command %q %q failed with %s\n", app, args, err) - } - - return string(out) -} - -// Outputs if docker container of the given name exists already -func CheckDockerExists(name string) (bool) { - arg := "name="+name - cmd := ExecuteCmd("docker", "ps", "-a", "--filter", arg) - // Convert the command output to array and check if name exists - return arrays.Contains(arrays.StringToArray(cmd), name) -} diff --git a/tools/errors/errors.go b/tools/errors/errors.go deleted file mode 100644 index bb412de..0000000 --- a/tools/errors/errors.go +++ /dev/null @@ -1,23 +0,0 @@ -// This package implements multiple errors specific to riotpot -package errors - -import ( - "fmt" - "log" -) - -const ( - EmulatorNotInstalled = "Emulator not installed." -) - -func Error(name string, errorString string) { - err := fmt.Errorf("[!] Error: %s\n > %s", name, errorString) - fmt.Println(err) -} - -// Check if there is an error and throw a fatal -func Raise(err error) { - if err != nil { - log.Fatalf("[!] Error: %v", err) - } -}