Browse Source

working on adding a database layer to the web tool

AETH-erial 10 months ago
parent
commit
7e9b6e89d0
7 changed files with 217 additions and 29 deletions
  1. 3 0
      .gitignore
  2. 3 2
      Makefile
  3. 39 0
      cmd/kyoketsu-web/kyoketsu-web.go
  4. 1 0
      go.mod
  5. 2 0
      go.sum
  6. 40 0
      pkg/scanner.go
  7. 129 27
      pkg/storage.go

+ 3 - 0
.gitignore

@@ -8,3 +8,6 @@ build/**
 
 *.html
 
+sqlite.db
+*.db
+

+ 3 - 2
Makefile

@@ -27,10 +27,11 @@
 .PHONY: build format test install coverage coverage-html 
 
 KYOKETSU = kyoketsu
+KYOKETSU_WEB = kyoketsu-web
 
 build:
-	go build -x -v -cover -o ./build/linux/$(KYOKETSU)/$(KYOKETSU) ./cmd/$(KYOKETSU)/$(KYOKETSU).go
-
+	go build -x -v -cover -o ./build/linux/$(KYOKETSU)/$(KYOKETSU) ./cmd/$(KYOKETSU)/$(KYOKETSU).go && \
+	go build -x -v -cover -o ./build/linux/$(KYOKETSU_WEB)/$(KYOKETSU_WEB) ./cmd/$(KYOKETSU_WEB)/$(KYOKETSU_WEB).go
 
 format:
 	go fmt ./...

+ 39 - 0
cmd/kyoketsu-web/kyoketsu-web.go

@@ -26,3 +26,42 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
 
 package main
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"os"
+
+	kyoketsu "git.aetherial.dev/aeth/kyoketsu/pkg"
+)
+
+const dbfile = "sqlite.db"
+
+func main() {
+	db, err := sql.Open("sqlite3", dbfile)
+	if err != nil {
+		log.Fatal(err)
+	}
+	hostsRepo := kyoketsu.NewSQLiteRepo(db)
+
+	if err = hostsRepo.Migrate(); err != nil {
+		log.Fatal(err)
+	}
+	log.Println("SUCCESS ::: SQLite database initiated, and open for writing.")
+
+	hosts, err := kyoketsu.NetSweep(os.Args[1], kyoketsu.RetrieveScanDirectives().Pairs)
+	if err != nil {
+		log.Fatal(err)
+	}
+	for i := range hosts {
+		fmt.Printf("%+v\n", hosts[i])
+		_, err := hostsRepo.Create(*hosts[i])
+		if err != nil {
+			log.Fatalf("Couldnt create new entry :( error: %s\n", err)
+
+		}
+
+	}
+
+}

+ 1 - 0
go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/uuid v1.2.0 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
+	github.com/mattn/go-sqlite3 v1.14.22 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.2 // indirect

+ 2 - 0
go.sum

@@ -8,6 +8,8 @@ github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=

+ 40 - 0
pkg/scanner.go

@@ -27,6 +27,7 @@ package kyoketsu
 import (
 	"fmt"
 	"net"
+	"strings"
 	"sync"
 	"time"
 )
@@ -47,6 +48,8 @@ type Host struct {
 	IpAddress      string         // the IPv4 address (no ipv6 support yet)
 	PingResponse   bool           // boolean value representing if the host responded to ICMP
 	ListeningPorts map[int]string // list of maps depicting a port number -> service name
+	PortString     string
+	Id             int64
 }
 
 /*
@@ -71,6 +74,7 @@ func PortWalk(addr string, portmap map[int]string) *Host {
 	for i := range out {
 		if out[i].Listening {
 			host.ListeningPorts[out[i].PortNumber] = out[i].Service
+			host.PortString = fmt.Sprintf("%s,%v", host.PortString, out[i].PortNumber)
 		}
 
 	}
@@ -115,3 +119,39 @@ func singlePortScan(addr string, port int, svcs string) *PortScanResult {
 	conn.Close()
 	return &PortScanResult{PortNumber: port, Protocol: "tcp", Service: svcs, Listening: true}
 }
+
+/*
+Perform a port scan sweep across an entire subnet
+
+	:param ip: the IPv4 address WITH CIDR notation
+	:param portmap: the mapping of ports to scan with (port number mapped to protocol name)
+*/
+func NetSweep(ip string, portmap map[int]string) ([]*Host, error) {
+	var err error
+	var addr *IpSubnetMapper
+	addr, err = GetNetworkAddresses(ip)
+	if err != nil {
+		return nil, err
+	}
+	returnhosts := []*Host{}
+
+	var wg sync.WaitGroup
+	mu := &sync.Mutex{}
+	for i := range addr.Ipv4s {
+		wg.Add(1)
+		go func(target string, mu *sync.Mutex) {
+			defer wg.Done()
+			out := PortWalk(target, portmap)
+			if len(out.ListeningPorts) > 0 {
+				dns, _ := net.LookupAddr(out.IpAddress)
+				out.Fqdn = strings.Join(dns, ", ")
+				mu.Lock()
+				returnhosts = append(returnhosts, out)
+				mu.Unlock()
+			}
+		}(addr.Ipv4s[i].String(), mu)
+
+	}
+	wg.Wait()
+	return returnhosts, nil
+}

+ 129 - 27
pkg/storage.go

@@ -28,12 +28,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
 package kyoketsu
 
 import (
-	"context"
-	"fmt"
-	"time"
+	"database/sql"
+	"errors"
 
-	"go.mongodb.org/mongo-driver/mongo"
-	"go.mongodb.org/mongo-driver/mongo/options"
+	"github.com/mattn/go-sqlite3"
 )
 
 type TopologyDatabaseIO interface {
@@ -42,40 +40,144 @@ type TopologyDatabaseIO interface {
 		    for an appropriate implementation of the data storage that the distributed system will use.
 		    When I get around to implementing the client-to-client format of this, it could be anything.
 	*/
-	AddHostToDb(*Host) error             // Add a host to the hosts table
-	UpdateHostEntry(string, *Host) error //Update a host entry, indexing by its ip address
-	RemoveHostEntry(string) error        // Remove a host from the database
+	Migrate() error
+	Create(host Host) (*Host, error)
+	All() ([]Host, error)
+	GetByFqdn(dn string) (*Host, error)
+	Update(id int64, updated Host) (*Host, error)
+	Delete(id int64) error
 }
 
-type MongoClient struct {
-	conn *mongo.Client
+var (
+	ErrDuplicate    = errors.New("record already exists")
+	ErrNotExists    = errors.New("row not exists")
+	ErrUpdateFailed = errors.New("update failed")
+	ErrDeleteFailed = errors.New("delete failed")
+)
+
+type SQLiteRepo struct {
+	db *sql.DB
 }
 
-func NewMongoClient(host string, port int) *MongoClient {
+// Instantiate a new SQLiteRepo struct
+func NewSQLiteRepo(db *sql.DB) *SQLiteRepo {
+	return &SQLiteRepo{
+		db: db,
+	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-	defer cancel()
-	client, err := mongo.Connect(ctx, options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%v", host, port)))
-	defer func() {
-		if err = client.Disconnect(ctx); err != nil {
-			panic(err)
+}
+
+// Creates a new SQL table with necessary data
+func (r *SQLiteRepo) Migrate() error {
+	query := `
+    CREATE TABLE IF NOT EXISTS hosts(
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        fqdn TEXT NOT NULL,
+        ipv4_address TEXT NOT NULL UNIQUE,
+        listening_port TEXT NOT NULL
+    );
+    `
+
+	_, err := r.db.Exec(query)
+	return err
+}
+
+/*
+Create an entry in the hosts table
+
+	:param host: a Host entry from a port scan
+*/
+func (r *SQLiteRepo) Create(host Host) (*Host, error) {
+	res, err := r.db.Exec("INSERT INTO hosts(fqdn, ipv4_address, listening_port) values(?,?,?)", host.Fqdn, host.IpAddress, host.PortString)
+	if err != nil {
+		var sqliteErr sqlite3.Error
+		if errors.As(err, &sqliteErr) {
+			if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
+				return nil, ErrDuplicate
+			}
 		}
-	}()
-	return &MongoClient{conn: client}
+		return nil, err
+	}
+
+	id, err := res.LastInsertId()
+	if err != nil {
+		return nil, err
+	}
+	host.Id = id
+
+	return &host, nil
 }
 
-func (m *MongoClient) addDocument(id string, data interface{}) error {
-	return nil
+// Get all Hosts from the host table
+func (r *SQLiteRepo) All() ([]Host, error) {
+	rows, err := r.db.Query("SELECT * FROM hosts")
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	var all []Host
+	for rows.Next() {
+		var host Host
+		if err := rows.Scan(&host.Id, &host.Fqdn, &host.IpAddress, &host.PortString); err != nil {
+			return nil, err
+		}
+		all = append(all, host)
+	}
+	return all, nil
 }
 
-func (m *MongoClient) AddHostToDb(host *Host) error {
-	return nil
+// Get a record by its FQDN
+func (r *SQLiteRepo) GetByFqdn(dn string) (*Host, error) {
+	row := r.db.QueryRow("SELECT * FROM hosts WHERE fqdn = ?", dn)
+
+	var host Host
+	if err := row.Scan(&host.Id, &host.Fqdn, &host.IpAddress, &host.PortString); err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			return nil, ErrNotExists
+		}
+		return nil, err
+	}
+	return &host, nil
 }
 
-func (m *MongoClient) UpdateHostEntry(id string, host *Host) error {
-	return nil
+// Update a record by its ID
+func (r *SQLiteRepo) Update(id int64, updated Host) (*Host, error) {
+	if id == 0 {
+		return nil, errors.New("invalid updated ID")
+	}
+	res, err := r.db.Exec("UPDATE hosts SET name = ?, url = ?, rank = ? WHERE id = ?", updated.Fqdn, updated.IpAddress, updated.PortString, id)
+	if err != nil {
+		return nil, err
+	}
+
+	rowsAffected, err := res.RowsAffected()
+	if err != nil {
+		return nil, err
+	}
+
+	if rowsAffected == 0 {
+		return nil, ErrUpdateFailed
+	}
+
+	return &updated, nil
 }
 
-func (m *MongoClient) RemoveHostEntry(id string) error {
-	return nil
+// Delete a record by its ID
+func (r *SQLiteRepo) Delete(id int64) error {
+	res, err := r.db.Exec("DELETE FROM hosts WHERE id = ?", id)
+	if err != nil {
+		return err
+	}
+
+	rowsAffected, err := res.RowsAffected()
+	if err != nil {
+		return err
+	}
+
+	if rowsAffected == 0 {
+		return ErrDeleteFailed
+	}
+
+	return err
 }