234 lines
5.7 KiB
Go
234 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
PRIVILEGE_OPT = "no-new-privileges=true"
|
|
ROOT_USER = "1000:1000"
|
|
|
|
FATAL IssueLevel = "FATAL"
|
|
WARNING IssueLevel = "WARNING"
|
|
PASSED IssueLevel = "PASSED"
|
|
)
|
|
|
|
type IssueLevel string
|
|
|
|
type RestartConfig struct {
|
|
Condition *string `yaml:"condition"`
|
|
Delay *string `yaml:"delay"`
|
|
MaxAttempts *int `yaml:"max_attempts"`
|
|
Window *string `yaml:"window"`
|
|
}
|
|
|
|
type DeployConfig struct {
|
|
Policy *RestartConfig `yaml:"restart_policy"`
|
|
}
|
|
|
|
type ServiceConfig struct {
|
|
MemLimit *string `yaml:"mem_limit"`
|
|
Cpus *float64 `yaml:"cpus"`
|
|
Ports *[]string `yaml:"ports"`
|
|
User *string `yaml:"user"`
|
|
Deploy *DeployConfig `yaml:"deploy"`
|
|
SecOpts *[]string `yaml:"security_opt"`
|
|
}
|
|
|
|
type Compose struct {
|
|
Services map[string]ServiceConfig `yaml:"services"`
|
|
}
|
|
|
|
func main() {
|
|
filePath := os.Getenv("COMPOSE_FILE_PATH")
|
|
if filePath == "" {
|
|
filePath = "./compose.yaml"
|
|
}
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
fd, err := io.ReadAll(f)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var data Compose
|
|
err = yaml.Unmarshal(fd, &data)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
type report struct {
|
|
Name string //service name
|
|
|
|
//restart policy
|
|
PolicyPresent bool //is it present
|
|
PolicyLevel IssueLevel //how serious
|
|
PolicyIssues []string
|
|
//are ports behind a proxy?
|
|
PortSafe bool
|
|
PortIssues []string
|
|
PortLevel IssueLevel
|
|
//memory limits and cpu
|
|
ResourceSafe bool
|
|
ResourceIssues []string
|
|
ResourceLevel IssueLevel
|
|
//proper security options
|
|
SecurityOptSafe bool
|
|
SecurityOptLevel IssueLevel
|
|
SecurityOptIssues []string
|
|
//are users nonroot
|
|
UserSafe bool
|
|
UserIssues []string
|
|
UserIssueLevel IssueLevel
|
|
}
|
|
|
|
var issues []report
|
|
for name, serviceConf := range data.Services {
|
|
r := report{
|
|
Name: name,
|
|
}
|
|
|
|
if serviceConf.Ports != nil {
|
|
matched, err := regexp.Match(`.*:.*:.*`, []byte((*serviceConf.Ports)[0]))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
portMatched, err := regexp.Match(`.*:.*`, []byte((*serviceConf.Ports)[0]))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if !matched {
|
|
r.PortSafe = false
|
|
r.PortIssues = append(r.PortIssues, "host port is fully exposed, consider adding an IP address to keep it behind the proxy")
|
|
r.PortLevel = WARNING
|
|
if !portMatched {
|
|
r.PortIssues = append(r.PortIssues, "ports seem misconfigured validate ports in compose file")
|
|
r.PortLevel = FATAL
|
|
}
|
|
} else {
|
|
r.PortSafe = true
|
|
r.PortLevel = PASSED
|
|
}
|
|
} else {
|
|
r.PortSafe = true
|
|
r.PortLevel = PASSED
|
|
}
|
|
|
|
r.ResourceSafe = true
|
|
r.ResourceLevel = PASSED
|
|
if serviceConf.MemLimit == nil {
|
|
r.ResourceSafe = false
|
|
r.ResourceIssues = append(r.ResourceIssues, "no memory limit has been set for the container")
|
|
r.ResourceLevel = WARNING
|
|
}
|
|
if serviceConf.Cpus == nil {
|
|
r.ResourceSafe = false
|
|
r.ResourceIssues = append(r.ResourceIssues, "no cpu limit set for the container")
|
|
r.ResourceLevel = WARNING
|
|
}
|
|
|
|
if serviceConf.Cpus == nil && serviceConf.MemLimit == nil {
|
|
r.ResourceLevel = FATAL
|
|
r.ResourceIssues = append(r.ResourceIssues, "no resource limits set, its the wild west")
|
|
}
|
|
|
|
if serviceConf.Deploy != nil && serviceConf.Deploy.Policy != nil {
|
|
r.PolicyPresent = true
|
|
r.PolicyLevel = PASSED
|
|
} else {
|
|
r.PolicyPresent = false
|
|
r.PolicyLevel = WARNING
|
|
//this should check fields bettera and suggest better conditions
|
|
r.PolicyIssues = append(r.PolicyIssues, "set a restart policy for the container")
|
|
}
|
|
|
|
r.SecurityOptSafe = false
|
|
if serviceConf.SecOpts != nil {
|
|
for _, opt := range *serviceConf.SecOpts {
|
|
if opt == PRIVILEGE_OPT {
|
|
r.SecurityOptSafe = true
|
|
r.SecurityOptLevel = PASSED
|
|
}
|
|
}
|
|
}
|
|
|
|
if !r.SecurityOptSafe {
|
|
r.SecurityOptLevel = FATAL
|
|
r.SecurityOptIssues = append(r.SecurityOptIssues, "set no-new-privileges=true on the container security_opts")
|
|
}
|
|
|
|
r.UserSafe = true
|
|
r.UserIssueLevel = PASSED
|
|
if serviceConf.User != nil {
|
|
matchnum, err := regexp.Match("[0-9].*:[0-9].*", []byte(*serviceConf.User))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if matchnum {
|
|
if strings.EqualFold(*serviceConf.User, ROOT_USER) {
|
|
r.UserSafe = false
|
|
r.UserIssueLevel = FATAL
|
|
r.UserIssues = append(r.UserIssues, "a root user is set in the compose file")
|
|
}
|
|
} else {
|
|
match, err := regexp.Match(".*:.*", []byte(*serviceConf.User))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if !match {
|
|
r.UserIssues = append(r.UserIssues, "user configuration seems off, check compose file")
|
|
r.UserIssueLevel = WARNING
|
|
r.UserSafe = false
|
|
}
|
|
}
|
|
} else {
|
|
r.UserSafe = false
|
|
r.UserIssues = append(r.UserIssues, "no user is set in the compose file")
|
|
r.UserIssueLevel = WARNING
|
|
}
|
|
|
|
issues = append(issues, r)
|
|
}
|
|
|
|
for _, p := range issues {
|
|
fmt.Println("##########################################")
|
|
fmt.Println(p.Name)
|
|
fmt.Println("------------------------------------------")
|
|
fmt.Println("[SECURITY_OPTS]")
|
|
fmt.Println(p.SecurityOptLevel)
|
|
fmt.Printf("safe: %t\n\n", p.SecurityOptSafe)
|
|
fmt.Println(strings.Join(p.SecurityOptIssues, "\n"))
|
|
fmt.Println("\n[USER]")
|
|
fmt.Println(p.UserIssueLevel)
|
|
fmt.Printf("safe: %t\n\n", p.UserSafe)
|
|
fmt.Println(strings.Join(p.UserIssues, "\n"))
|
|
fmt.Println("\n[RESOURCE]")
|
|
fmt.Println(p.ResourceLevel)
|
|
fmt.Printf("safe: %t\n\n", p.ResourceSafe)
|
|
fmt.Println(strings.Join(p.ResourceIssues, "\n"))
|
|
fmt.Println("\n[PORT]")
|
|
fmt.Println(p.PortLevel)
|
|
fmt.Printf("safe: %t\n\n", p.PortSafe)
|
|
fmt.Println(strings.Join(p.PortIssues, "\n"))
|
|
fmt.Println("\n[POLICY]")
|
|
fmt.Println(p.PolicyLevel)
|
|
fmt.Printf("safe: %t\n\n", p.PolicyPresent)
|
|
fmt.Println(strings.Join(p.PolicyIssues, "\n"))
|
|
}
|
|
}
|