diff --git a/Makefile b/Makefile index 97b4d7d..7ba8d30 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ test: go test -v apps/*.go go test -v apps/drivers/*.go + go test -v detector/*.go + go test -v docker/*.go build: #podman run --rm --privileged -ti -v ${shell pwd}:/srv docker.io/library/golang:1.14-stretch /bin/sh -c "cd /srv && go build" @@ -18,7 +20,7 @@ minio: -p 9001:9001 \ -e MINIO_ROOT_USER=test \ -e MINIO_ROOT_PASSWORD=testtest \ - minio/minio server /data --console-address ":9001" + docker.io/minio/minio:latest server /data --console-address ":9001" .PHONY: clean clean: diff --git a/README.md b/README.md index e171035..9db98a9 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,13 @@ Node API is an microservice that runs on node servers. It provides interface between Docker and the admin site. + + +## Test + +On Fedora run podman API: + + Root: sudo systemctl enable --now podman.socket + Rootless: podman system service -t 0 --log-level=debug + + diff --git a/apps/main.go b/apps/main.go index b71b48e..401c14e 100644 --- a/apps/main.go +++ b/apps/main.go @@ -3,6 +3,7 @@ package apps import ( "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" + "github.com/rosti-cz/node-api/detector" ) // AppsProcessor encapsulates functions for apps manipulation @@ -109,13 +110,14 @@ func (a *AppsProcessor) Update(name string, SSHPort int, HTTPPort int, image str } // UpdateResources updates various metrics saved in the database -func (a *AppsProcessor) UpdateResources(name string, state string, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int) error { +func (a *AppsProcessor) UpdateResources(name string, state string, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags) error { err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{ State: state, CPUUsage: CPUUsage, MemoryUsage: memory, DiskUsageBytes: diskUsageBytes, DiskUsageInodes: diskUsageInodes, + Flags: flags, }).Error return err } diff --git a/apps/types.go b/apps/types.go index 4a09f1c..e3cad66 100644 --- a/apps/types.go +++ b/apps/types.go @@ -7,6 +7,7 @@ import ( // This is line from GORM documentation that imports database dialect _ "github.com/jinzhu/gorm/dialects/sqlite" + "github.com/rosti-cz/node-api/detector" ) // ValidationError is error that holds multiple validation error messages @@ -31,11 +32,12 @@ type Label struct { // AppState contains info about runnint application, it's not saved in the database type AppState struct { - State string `json:"state"` - CPUUsage float64 `json:"cpu_usage"` // in percents - MemoryUsage int `json:"memory_usage"` // in MB - DiskUsageBytes int `json:"disk_usage_bytes"` - DiskUsageInodes int `json:"disk_usage_inodes"` + State string `json:"state"` + CPUUsage float64 `json:"cpu_usage"` // in percents + MemoryUsage int `json:"memory_usage"` // in MB + DiskUsageBytes int `json:"disk_usage_bytes"` + DiskUsageInodes int `json:"disk_usage_inodes"` + Flags detector.Flags `json:"flags"` } // Apps is list of applications @@ -83,6 +85,8 @@ type App struct { DiskUsageBytes int `json:"disk_usage_bytes"` // Disk usage in inodes DiskUsageInodes int `json:"disk_usage_inodes"` + // Flags from detector of problems in the container + Flags detector.Flags `json:"flags"` // this is gathered in docker package and has to be assembled externally Techs AppTechs `json:"techs,omitempty" gorm:"-"` // list of available technologies in the image diff --git a/detector/main.go b/detector/main.go new file mode 100644 index 0000000..9aa755b --- /dev/null +++ b/detector/main.go @@ -0,0 +1,37 @@ +package detector + +import ( + "fmt" + "regexp" +) + +// Flags is list of strings describing problems found among the processes +type Flags []string + +// Check goes over patterns and tries to flag given list of processes with flags. +func Check(processes []string) (Flags, error) { + flags := Flags{} + tmpFlags := make(map[string]bool) + + for _, process := range processes { + for flag, patternSet := range patterns { + for _, pattern := range patternSet { + matched, err := regexp.MatchString(".*"+pattern+".*", process) + if err != nil { + return flags, err + } + if matched { + tmpFlags[flag] = true + break + } + fmt.Println(process, pattern, flag) + } + } + } + + for flag, _ := range tmpFlags { + flags = append(flags, flag) + } + + return flags, nil +} diff --git a/detector/main_test.go b/detector/main_test.go new file mode 100644 index 0000000..66f263b --- /dev/null +++ b/detector/main_test.go @@ -0,0 +1,35 @@ +package detector + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheck(t *testing.T) { + flags, err := Check([]string{ + "sleep", + "apache2", + "miner", + "verus-solve", + }) + assert.Nil(t, err) + assert.Contains(t, flags, "miner") + + flags, err = Check([]string{ + "sleep", + "apache2", + "hellminer", + }) + assert.Nil(t, err) + assert.Contains(t, flags, "miner") + + flags, err = Check([]string{ + "sleep", + "apache2", + "miner", // This is not among patterns map + }) + assert.Nil(t, err) + assert.NotContains(t, flags, "miner") + +} diff --git a/detector/patterns.go b/detector/patterns.go new file mode 100644 index 0000000..23dad28 --- /dev/null +++ b/detector/patterns.go @@ -0,0 +1,8 @@ +package detector + +var patterns map[string][]string = map[string][]string{ + "miner": { + `verus\-solve`, + `hellminer`, + }, +} diff --git a/docker/docker.go b/docker/docker.go index 12a0d71..db9839b 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "log" @@ -17,6 +18,7 @@ import ( "github.com/docker/docker/api/types/network" dockerClient "github.com/docker/docker/client" "github.com/docker/go-connections/nat" + "github.com/rosti-cz/node-api/detector" ) // Stats delay in seconds @@ -27,7 +29,7 @@ const dockerTimeout = 10 // DOCKER_SOCK tells where to connect to docker, it will be always local sock const dockerSock = "/var/run/docker.sock" -const podmanSock = "/run/podman/podman.sock" +const podmanSock = "/run/user/1000/podman/podman.sock" // DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11 const dockerAPIVersion = "1.38" @@ -47,7 +49,10 @@ func (d *Driver) getClient() (*dockerClient.Client, error) { } cli, err := dockerClient.NewClient("unix://"+connectTo, dockerAPIVersion, nil, nil) - return cli, err + if err != nil { + return cli, fmt.Errorf("get docker client error: %v", err) + } + return cli, nil } // ConnectionStatus checks connection to the Docker daemon @@ -448,3 +453,43 @@ func (d *Driver) Exec(name string, cmd []string, stdin string, env []string, att return &stdouterr, err } + +// GetProcesses return list of processes running under this container +func (d *Driver) GetProcesses(name string) ([]string, error) { + processes := []string{} + + ctx := context.Background() + + cli, err := d.getClient() + if err != nil { + return processes, err + } + defer cli.Close() + + processList, err := cli.ContainerTop(ctx, name, []string{"-eo", "args"}) + if err != nil { + return processes, fmt.Errorf("docker container top call error: %v", err) + } + + for _, process := range processList.Processes { + if len(process) > 0 { + processes = append(processes, process[0]) + } + } + + return processes, nil +} + +// GetFlags returns list of flags with problems found in the container, mainly used to detect miners or viruses +func (d *Driver) GetFlags(name string) (detector.Flags, error) { + processes, err := d.GetProcesses(name) + if err != nil { + return detector.Flags{}, err + } + flags, err := detector.Check(processes) + if err != nil { + return flags, err + } + + return flags, nil +} diff --git a/docker/docker_test.go b/docker/docker_test.go new file mode 100644 index 0000000..da85e00 --- /dev/null +++ b/docker/docker_test.go @@ -0,0 +1,28 @@ +package docker + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetProcesses(t *testing.T) { + driver := Driver{ + BindIPHTTP: "127.0.0.1", + BindIPSSH: "127.0.0.1", + } + + driver.Remove("test") + + _, err := driver.Create("test", "docker.io/library/busybox", "/tmp", 8990, 8922, 1, 128, []string{"sleep", "3600"}) + assert.Nil(t, err) + + err = driver.Start("test") + assert.Nil(t, err) + + processes, err := driver.GetProcesses("test") + assert.Nil(t, err) + assert.Contains(t, processes, "sleep 3600") + + driver.Remove("test") +} diff --git a/stats.go b/stats.go index d93ab66..1434176 100644 --- a/stats.go +++ b/stats.go @@ -35,6 +35,7 @@ func updateUsage(name string) error { state.MemoryUsage, state.DiskUsageBytes, state.DiskUsageInodes, + state.Flags, ) return err