diff --git a/Shifud/Dockerfile b/Shifud/Dockerfile new file mode 100644 index 000000000..7bb6f8a41 --- /dev/null +++ b/Shifud/Dockerfile @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.19.2-alpine + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +# Install required packages +RUN apk add --no-cache gcc musl-dev libc-dev + +COPY . . + +RUN for f in plugins/*.go; do go build -buildmode=plugin -o "${f%.go}.so" "$f"; done + +RUN go build -o main main.go + +CMD ["./main"] diff --git a/Shifud/ReadMe.md b/Shifud/ReadMe.md new file mode 100644 index 000000000..f7eeba4d3 --- /dev/null +++ b/Shifud/ReadMe.md @@ -0,0 +1,82 @@ +# Shifud +## How do I run it locally +```bash +for f in plugins/*.go; do go build -buildmode=plugin -o "${f%.go}.so" "$f"; done +go run main.go +``` + +## Running in docker +```bash +docker build -t shifud . +docker run -p 8080:8080 shifud +``` + +## Shifud Plugin Development Guide +Shifud is a flexible network scanning tool with an extensible plugin system. This guide will help you create and integrate a new plugin into the Shifud system. + +## Understanding the Plugin System + +Shifud uses Go's `plugin` package to dynamically load plugins at runtime. Plugins are compiled as shared libraries (`.so` files) and loaded from the `plugins` folder. Each plugin must implement the `ScannerPlugin` interface, which is defined in the `shifud.go` file: + +```go +type ScannerPlugin interface { + Scan() ([]DeviceConfig, error) +} +``` + +## Creating a New Plugin +1. ** folder.** Name the file based on your plugin's functionality, e.g., `http_scanner.go`. +2. ** interface.** For example: + +```go +package plugins + +import ( + "example.com/Shifud/shifud" + "fmt" +) + +type HttpScanner struct{} + +func (s *HttpScanner) Scan() ([]shifud.DeviceConfig, error) { + fmt.Println("Scanning with HTTP scanner...") + // Add your plugin logic here + return nil, nil +} + +var ScannerPlugin HttpScanner +``` + + +** function.** This is where you'll add the core functionality of your plugin. The `Scan()` function should return a slice of `DeviceConfig` objects and an error if something goes wrong. +## Compiling and Loading the Plugin +**Compile the plugin as a shared library.** Use the following command to compile your plugin: + +```bash +go build -buildmode=plugin -o plugins/*.so plugins/*.go +``` + +You can also build all plugins yourself with the following command: +```bash +for f in plugins/*.go; do go build -buildmode=plugin -o "${f%.go}.so" "$f"; done +``` + + + +Replace `your_plugin_name` with the appropriate name for your plugin. +**file.** The `LoadPlugins` function in the `shifud.go` file handles the loading of plugins. It is called in the `main.go` file as follows: + +```go +err := shifud.LoadPlugins(scanner, "plugins") +if err != nil { + fmt.Println("Error loading plugins:", err) + os.Exit(1) +} +``` + + + +With these steps, your new plugin will be loaded and used by Shifud at runtime. You can create additional plugins by following the same process, and Shifud will automatically load and use them. +## Testing Your Plugin + +After creating and loading your plugin, run the `main.go` file to test your plugin's functionality. If everything is set up correctly, you should see the output of your plugin's `Scan()` function when Shifud runs. \ No newline at end of file diff --git a/Shifud/devices.yml b/Shifud/devices.yml new file mode 100644 index 000000000..19765bd50 --- /dev/null +++ b/Shifud/devices.yml @@ -0,0 +1 @@ +null diff --git a/Shifud/go.mod b/Shifud/go.mod new file mode 100644 index 000000000..ad3c6315d --- /dev/null +++ b/Shifud/go.mod @@ -0,0 +1,12 @@ +module example.com/Shifud + +go 1.17 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/Shifud/go.sum b/Shifud/go.sum new file mode 100644 index 000000000..b09ce3d27 --- /dev/null +++ b/Shifud/go.sum @@ -0,0 +1,20 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/Shifud/main.go b/Shifud/main.go new file mode 100644 index 000000000..72dcba8c6 --- /dev/null +++ b/Shifud/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "example.com/Shifud/shifud" + "fmt" + _ "net" + "os" + "sigs.k8s.io/yaml" +) + +func main() { + fmt.Println("Shifud online...") + + // Create a scanner + scanner := &shifud.Scanner{} + + // Load plugins from the plugins folder + err := shifud.LoadPlugins(scanner, "plugins") + if err != nil { + fmt.Println("Error loading plugins:", err) + os.Exit(1) + } + + // Scan devices with all plugins + devices, err := scanner.Scan() + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + // Export the configuration settings to a YAML file + output, err := yaml.Marshal(devices) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + err = os.WriteFile("devices.yml", output, 0644) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } +} diff --git a/Shifud/plugincommon/basewebscanner.go b/Shifud/plugincommon/basewebscanner.go new file mode 100644 index 000000000..a2f3ee593 --- /dev/null +++ b/Shifud/plugincommon/basewebscanner.go @@ -0,0 +1,40 @@ +package plugincommon + +import ( + "example.com/Shifud/shifud" + "fmt" + "log" + "sync" + "time" +) + +type ProtocolHandler func(ip string, port int, timeout time.Duration) bool + +func WebScanner(startPort, endPort int, timeout time.Duration, handler ProtocolHandler) []shifud.DeviceConfig { + var devices []shifud.DeviceConfig + var wg sync.WaitGroup + + log.Println("Launching our little scanner minions! 🚀") + for i := 1; i <= 254; i++ { + ip := fmt.Sprintf("192.168.1.%d", i) + wg.Add(1) + + go func(ip string) { + defer wg.Done() + + for port := startPort; port <= endPort; port++ { + if handler(ip, port, timeout) { + devices = append(devices, shifud.DeviceConfig{ + IP: ip, + Port: port, + }) + log.Printf("Minion found a device at %s:%d! High five! 🙌\n", ip, port) + } + } + }(ip) + } + + wg.Wait() + log.Println("All minions have returned! Time to count our treasures! 💎") + return devices +} diff --git a/Shifud/plugins/httpscanner.go b/Shifud/plugins/httpscanner.go new file mode 100644 index 000000000..2645f9b80 --- /dev/null +++ b/Shifud/plugins/httpscanner.go @@ -0,0 +1,42 @@ +package main + +import ( + "example.com/Shifud/plugincommon" + "example.com/Shifud/shifud" + "log" + "net" + "net/http" + "strconv" + "time" +) + +type HttpScanner struct{} + +func (s *HttpScanner) Scan() ([]shifud.DeviceConfig, error) { + startPort := 80 + endPort := 10000 + timeout := 5 * time.Second + handler := httpHandler + log.Println("Scanning for HTTP devices in the wild... Let's see what we find!") + devices := plugincommon.WebScanner(startPort, endPort, timeout, handler) + log.Printf("Finished scanning HTTP devices! Found %d devices. Time to party! 🥳\n", len(devices)) + return devices, nil +} + +func httpHandler(ip string, port int, timeout time.Duration) bool { + address := ip + ":" + strconv.Itoa(port) + conn, err := net.DialTimeout("tcp", address, timeout) + if err == nil { + defer conn.Close() + + // Check if it's an HTTP server + _, err := http.Get("http://" + address) + if err == nil { + log.Printf("HTTP server spotted at %s! 🎯\n", address) + return true + } + } + return false +} + +var ScannerPlugin HttpScanner diff --git a/Shifud/plugins/httpsscanner.go b/Shifud/plugins/httpsscanner.go new file mode 100644 index 000000000..29db7c2b4 --- /dev/null +++ b/Shifud/plugins/httpsscanner.go @@ -0,0 +1,34 @@ +package main + +import ( + "crypto/tls" + "example.com/Shifud/plugincommon" + "example.com/Shifud/shifud" + "net" + "strconv" + "time" +) + +type HttpsScanner struct{} + +func (s *HttpsScanner) Scan() ([]shifud.DeviceConfig, error) { + startPort := 80 + endPort := 10000 + timeout := 5 * time.Second + handler := httpsHandler + devices := plugincommon.WebScanner(startPort, endPort, timeout, handler) + return devices, nil +} + +func httpsHandler(ip string, port int, timeout time.Duration) bool { + address := ip + ":" + strconv.Itoa(port) + dialer := &net.Dialer{Timeout: timeout} + conn, err := tls.DialWithDialer(dialer, "tcp", address, &tls.Config{InsecureSkipVerify: true}) + if err == nil { + defer conn.Close() + return true + } + return false +} + +var ScannerPlugin HttpsScanner diff --git a/Shifud/shifud/shifud.go b/Shifud/shifud/shifud.go new file mode 100644 index 000000000..1196e0d79 --- /dev/null +++ b/Shifud/shifud/shifud.go @@ -0,0 +1,115 @@ +package shifud + +import ( + "fmt" + "io/ioutil" + "os" + "plugin" + "sync" + "time" + + "sigs.k8s.io/yaml" +) + +type DeviceConfig struct { + IP string `yaml:"ip"` + Port int `yaml:"port"` + Option string `yaml:"option"` +} + +type ScannerPlugin interface { + Scan() ([]DeviceConfig, error) +} + +type Scanner struct { + plugins []ScannerPlugin +} + +func (s *Scanner) AddPlugin(p ScannerPlugin) { + s.plugins = append(s.plugins, p) +} + +func (s *Scanner) Scan() ([]DeviceConfig, error) { + var devices []DeviceConfig + var mu sync.Mutex + var wg sync.WaitGroup + + fmt.Println("Hold on to your hat, we're starting the scanning party! 🎉") + + // Scan with each plugin in parallel + for i, p := range s.plugins { + wg.Add(1) + go func(plugin ScannerPlugin, index int) { + defer wg.Done() + + start := time.Now() + ds, err := plugin.Scan() + duration := time.Since(start) + + if err != nil { + fmt.Printf("Oops! Plugin %d stumbled a bit: %v\n", index, err) + } else { + fmt.Printf("Plugin %d finished scanning in %v! 🚀\n", index, duration) + } + + output, err := yaml.Marshal(ds) + if err != nil { + fmt.Printf("Plugin %d had trouble packing its bags: %v\n", index, err) + } else { + filename := fmt.Sprintf("devices_plugin_%d.yml", index) + err = os.WriteFile(filename, output, 0644) + if err != nil { + fmt.Printf("Plugin %d's suitcase got lost: %v\n", index, err) + } else { + fmt.Printf("Plugin %d's results are ready for you in %s! 📦\n", index, filename) + } + } + + mu.Lock() + devices = append(devices, ds...) + mu.Unlock() + }(p, i) + } + + wg.Wait() + + fmt.Println("The scanning party is over. Thanks for joining! 🥳") + + return devices, nil +} + +func LoadPlugins(scanner *Scanner, pluginDir string) error { + files, err := ioutil.ReadDir(pluginDir) + if err != nil { + return err + } + + fmt.Println("Knocking on the plugins' doors... 🚪") + + for _, file := range files { + if !file.IsDir() && file.Name()[len(file.Name())-3:] == ".so" { + p, err := plugin.Open(pluginDir + "/" + file.Name()) + if err != nil { + return err + } + + symScannerPlugin, err := p.Lookup("ScannerPlugin") + if err != nil { + return err + } + + scannerPlugin, ok := symScannerPlugin.(ScannerPlugin) + if !ok { + return fmt.Errorf("unexpected type from module symbol") + } + + scanner.AddPlugin(scannerPlugin) + + fmt.Printf("Plugin %s is ready to join the fun! 🎊\n", file.Name()) + } + } + + fmt.Println("All the plugins are on board! Let's get this party started! 🕺") + + return nil +} diff --git a/Shifud/tests/scanner_test.go b/Shifud/tests/scanner_test.go new file mode 100644 index 000000000..694ca6f5f --- /dev/null +++ b/Shifud/tests/scanner_test.go @@ -0,0 +1,51 @@ +package tests + +import ( + "example.com/Shifud/shifud" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestScanner(t *testing.T) { + scanner := &shifud.Scanner{} + scanner.AddPlugin(&LocalNetworkScannerMock{}) + + devices, err := scanner.Scan() + assert.NoError(t, err) + assert.NotNil(t, devices) + assert.Len(t, devices, 2) + + expectedDevices := []shifud.DeviceConfig{ + { + IP: "192.168.1.2", + Port: 8080, + Option: "default", + }, + { + IP: "192.168.1.3", + Port: 8080, + Option: "default", + }, + } + + assert.ElementsMatch(t, expectedDevices, devices) +} + +type LocalNetworkScannerMock struct{} + +func (s *LocalNetworkScannerMock) Scan() ([]shifud.DeviceConfig, error) { + devices := []shifud.DeviceConfig{ + { + IP: "192.168.1.2", + Port: 8080, + Option: "default", + }, + { + IP: "192.168.1.3", + Port: 8080, + Option: "default", + }, + } + + return devices, nil +} diff --git a/devices.yml b/devices.yml new file mode 100644 index 000000000..047976a14 --- /dev/null +++ b/devices.yml @@ -0,0 +1,3 @@ +- IP: 192.168.2.24 + Option: default + Port: 8080