diff --git a/main.go b/main.go index ae685a7..90fbc5a 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ const ( ROOT_USER_GROUP = "1000:1000" ROOT_USER = "1000" ROOT_GROUP = "1000" + IM_SO_SORRY = `^(\${{\s*(vars|secrets)\..*}}|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):(\${{\s*(vars|secrets)\..*}}|[0-9]+):(\${{\s*(vars|secrets)\..*}}|[0-9]+)` FATAL IssueLevel = "FATAL" WARNING IssueLevel = "WARNING" @@ -45,14 +46,6 @@ func (i *Issue) Fatal() { i.Safe = false } -//have functions that take the service configuration and they check certain fields then -//will return an issue if there is one, which we will append. - -//then loop through to print - -//this should be a struct w methods like Passed() that handles the true/FATAL stuff -//then the printing and stuff can just be loops it doesn't have to be so hardcoded - type RestartConfig struct { Condition *string `yaml:"condition"` Delay *string `yaml:"delay"` @@ -65,12 +58,13 @@ type DeployConfig struct { } 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"` + 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"` + Privileged *bool `yaml:"privileged"` } type Compose struct { @@ -99,48 +93,44 @@ func main() { log.Fatal(err) } - type report struct { - Name string //service name + // 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 - } + // //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 + // } type NewReport struct { Name string Issues []*Issue } - // var issues []report var issues []NewReport for name, serviceConf := range data.Services { - // var i NewReport rprt := NewReport{ Name: name, } - // i.Name = name - // _ = serviceConf + i := ResourceCheck(serviceConf) if i != nil { - // issues = append(issues, NewReport{Name: name, Issues: []*Issue{i}}) rprt.Issues = append(rprt.Issues, i) } @@ -157,130 +147,20 @@ func main() { rprt.Issues = append(rprt.Issues, k) } + l, err := PortCheck(serviceConf) + if err != nil { + log.Fatal(err) + } + if len(l) > 0 { + rprt.Issues = append(rprt.Issues, l...) + } + + m := PolicyCheck(serviceConf) + if m != nil { + rprt.Issues = append(rprt.Issues, m) + } + issues = append(issues, rprt) - // if serviceConf.MemLimit == nil { - // i.Issues = append(i.Issues, Issue{ - // Level: WARNING, - // Safe: true, - // }) - // // 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 - // } - - // 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 { @@ -289,30 +169,6 @@ func main() { fmt.Println(*x) } } - // 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")) - // } } func ResourceCheck(srv ServiceConfig) *Issue { @@ -333,6 +189,26 @@ func ResourceCheck(srv ServiceConfig) *Issue { } i.Passed() + i.Messages = append(i.Messages, "resource limits configuration is safe") + return i +} + +func PrivilegedCheck(srv ServiceConfig) *Issue { + i := &Issue{} + if srv.Privileged == nil { + i.Passed() + i.Messages = append(i.Messages, "no privileged flag set") + return i + } + + if *srv.Privileged == true { + i.Fatal() + i.Messages = append(i.Messages, "privileged flag is set to true") + return i + } + + i.Passed() + i.Messages = append(i.Messages, "privileged flag is set to false") return i } @@ -347,6 +223,7 @@ func SecurityOptCheck(srv ServiceConfig) *Issue { for _, opt := range *srv.SecOpts { if strings.EqualFold(opt, PRIVILEGE_OPT) { i.Passed() + i.Messages = append(i.Messages, "security option are safe") return i } } @@ -356,6 +233,51 @@ func SecurityOptCheck(srv ServiceConfig) *Issue { return i } +func PortCheck(srv ServiceConfig) ([]*Issue, error) { + il := []*Issue{} + if srv.Ports == nil { + i := &Issue{} + i.Passed() + i.Messages = append(i.Messages, "no ports exposed") + il = append(il, i) + return il, nil + } + + for _, prt := range *srv.Ports { + i := &Issue{} + m, err := regexp.Match(`^[0-9]*:[0-9]*`, []byte(prt)) + if err != nil { + return nil, err + } + + if m { + i.Fatal() + i.Messages = append(i.Messages, fmt.Sprintf("%s is fully exposed", prt)) + il = append(il, i) + continue + } + + m2, err := regexp.Match(IM_SO_SORRY, []byte(prt)) + if err != nil { + return nil, err + } + + if m2 { + i.Passed() + i.Messages = append(i.Messages, fmt.Sprintf("%s is safe", prt)) + il = append(il, i) + continue + } else { + i.Warning() + i.Messages = append(i.Messages, fmt.Sprintf("unable to parse port configuration for %s", prt)) + il = append(il, i) + continue + } + } + + return il, nil +} + func UserCheck(srv ServiceConfig) (*Issue, error) { i := &Issue{} if srv.User == nil { @@ -364,7 +286,7 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { return i, nil } - mtch, err := regexp.Match("[0-9].*:[0-9].*", []byte(*srv.User)) + mtch, err := regexp.Match("[0-9]*:[0-9]*", []byte(*srv.User)) if err != nil { return nil, err } @@ -375,6 +297,7 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { i.Messages = append(i.Messages, "a root user set in the compose file") } else { i.Passed() + i.Messages = append(i.Messages, "user configuration is safe") } return i, nil } else { @@ -396,6 +319,7 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { if leftUser && rightUser { i.Passed() + i.Messages = append(i.Messages, "user configuration is safe") return i, nil } @@ -405,6 +329,7 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { i.Messages = append(i.Messages, "a root user is present in configuration") } else { i.Passed() + i.Messages = append(i.Messages, "user configuration is safe") } return i, nil } @@ -415,6 +340,7 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { i.Messages = append(i.Messages, "a root user is present in configuration") } else { i.Passed() + i.Messages = append(i.Messages, "user configuration is safe") } return i, nil } @@ -425,59 +351,33 @@ func UserCheck(srv ServiceConfig) (*Issue, error) { return i, nil } -// func PortCheck(srv ServiceConfig) (*Issue, error) { -// i := &Issue{} -// if srv.Ports == nil { -// i.Passed() -// return i, nil -// } +func PolicyCheck(srv ServiceConfig) *Issue { + i := &Issue{} + if srv.Deploy != nil { + if srv.Deploy.Policy != nil { + if srv.Deploy.Policy.Condition != nil { + if srv.Deploy.Policy.MaxAttempts != nil { + i.Passed() + i.Messages = append(i.Messages, "restart policy configuration is safe") + return i + } else { + i.Warning() + i.Messages = append(i.Messages, "no max_attempts value set on restart policy container could death loop") + return i + } + } else { + i.Fatal() + i.Messages = append(i.Messages, "no restart condition is set on the container") + return i + } + } else { + i.Fatal() + i.Messages = append(i.Messages, "no restart policy is set on the container") + return i + } + } -// for _, p := range *srv.Ports { -// mtc, err := regexp.Match(`.*:.*:.*`, []byte(p)) -// if err != nil { -// return nil, err -// } - -// if mtc { -// continue -// } - -// pmtc, err := regexp.Match(`.*:.*`, []byte(p)) -// if err != nil { -// return nil, err -// } - -// if pmtc { -// i.Warning() -// i.Messages = append(i.Messages, "ports are fully exposed, use an IP for the host port to move behind the proxy") -// } -// } - -// // 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 -// // } -// } + i.Fatal() + i.Messages = append(i.Messages, "no restart policy is set on the container") + return i +}