From 3638ad8c7401fa3a89173749ca6853dcdaa5b1ac Mon Sep 17 00:00:00 2001 From: jake Date: Sun, 9 Nov 2025 16:10:01 -0500 Subject: [PATCH] made progress - better printing - more checks - tests --- main.go | 165 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 09db35d..b5e5672 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,25 @@ 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"` @@ -24,7 +37,9 @@ 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 { @@ -38,28 +53,44 @@ func main() { } f, err := os.Open(filePath) if err != nil { - panic(err) + log.Fatal(err) } defer f.Close() fd, err := io.ReadAll(f) if err != nil { - panic(err) + log.Fatal(err) } var data Compose err = yaml.Unmarshal(fd, &data) if err != nil { - panic(err) + log.Fatal(err) } type report struct { - Name string - PolicyPresent bool - PortSafety bool - PortIssues []string - ResourceSafety bool + 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 @@ -71,36 +102,132 @@ func main() { if serviceConf.Ports != nil { matched, err := regexp.Match(`.*:.*:.*`, []byte((*serviceConf.Ports)[0])) if err != nil { - panic(err) + log.Fatal(err) + } + + portMatched, err := regexp.Match(`.*:.*`, []byte((*serviceConf.Ports)[0])) + if err != nil { + log.Fatal(err) } if !matched { - r.PortSafety = false - r.PortIssues = append(r.PortIssues, "host port does not have an IP addressed and is fully opened") + 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.PortSafety = true + r.PortSafe = true + r.PortLevel = PASSED } } else { - r.PortSafety = true + r.PortSafe = true + r.PortLevel = PASSED } - r.ResourceSafety = true + r.ResourceSafe = true + r.ResourceLevel = PASSED if serviceConf.MemLimit == nil { - r.ResourceSafety = false - r.ResourceIssues = append(r.ResourceIssues, "no memory limit set") + 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.ResourceSafety = false - r.ResourceIssues = append(r.ResourceIssues, "no cpu limit set") + 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, "a nonroot user should be set") + r.UserIssueLevel = FATAL } - fmt.Printf("%+v\n", r) 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")) + } }