breaking push

- this push breaks what little functionality we had
- WIP push
- cleaning house, stand by
This commit is contained in:
2025-11-10 16:37:49 -05:00
parent 979892ef1e
commit b7389117ac
2 changed files with 374 additions and 120 deletions

490
main.go
View File

@@ -12,8 +12,10 @@ import (
)
const (
PRIVILEGE_OPT = "no-new-privileges=true"
ROOT_USER = "1000:1000"
PRIVILEGE_OPT = "no-new-privileges=true"
ROOT_USER_GROUP = "1000:1000"
ROOT_USER = "1000"
ROOT_GROUP = "1000"
FATAL IssueLevel = "FATAL"
WARNING IssueLevel = "WARNING"
@@ -22,6 +24,35 @@ const (
type IssueLevel string
type Issue struct {
Level IssueLevel
Safe bool
Messages []string
}
func (i *Issue) Passed() {
i.Level = PASSED
i.Safe = true
}
func (i *Issue) Warning() {
i.Level = WARNING
i.Safe = true
}
func (i *Issue) Fatal() {
i.Level = 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"`
@@ -49,7 +80,7 @@ type Compose struct {
func main() {
filePath := os.Getenv("COMPOSE_FILE_PATH")
if filePath == "" {
filePath = "./compose.yaml"
filePath = "compose.yaml"
}
f, err := os.Open(filePath)
if err != nil {
@@ -93,141 +124,360 @@ func main() {
UserIssueLevel IssueLevel
}
var issues []report
type NewReport struct {
Name string
Issues []*Issue
}
// var issues []report
var issues []NewReport
for name, serviceConf := range data.Services {
r := report{
// var i NewReport
rprt := NewReport{
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
// 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)
}
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
j := SecurityOptCheck(serviceConf)
if j != nil {
rprt.Issues = append(rprt.Issues, j)
}
if serviceConf.Cpus == nil && serviceConf.MemLimit == nil {
r.ResourceLevel = FATAL
r.ResourceIssues = append(r.ResourceIssues, "no resource limits set, its the wild west")
k, err := UserCheck(serviceConf)
if err != nil {
log.Fatal(err)
}
if k != nil {
rprt.Issues = append(rprt.Issues, k)
}
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")
}
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.SecurityOptSafe = false
if serviceConf.SecOpts != nil {
for _, opt := range *serviceConf.SecOpts {
if opt == PRIVILEGE_OPT {
r.SecurityOptSafe = true
r.SecurityOptLevel = PASSED
}
}
}
// r := report{
// Name: name,
// }
if !r.SecurityOptSafe {
r.SecurityOptLevel = FATAL
r.SecurityOptIssues = append(r.SecurityOptIssues, "set no-new-privileges=true on the container security_opts")
}
// if serviceConf.Ports != nil {
// matched, err := regexp.Match(`.*:.*:.*`, []byte((*serviceConf.Ports)[0]))
// if err != nil {
// log.Fatal(err)
// }
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)
}
// portMatched, err := regexp.Match(`.*:.*`, []byte((*serviceConf.Ports)[0]))
// 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 !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
// }
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
}
// 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
// }
issues = append(issues, r)
// 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"))
for _, x := range p.Issues {
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 {
i := &Issue{}
if srv.Cpus == nil {
i.Warning()
i.Messages = append(i.Messages, "there is no cpu limit set for the service")
}
if srv.MemLimit == nil {
i.Warning()
i.Messages = append(i.Messages, "there is no memory limit set for the service")
}
if srv.Cpus == nil && srv.MemLimit == nil {
i.Fatal()
i.Messages = append(i.Messages, "there are no resource limits set for the service")
}
i.Passed()
return i
}
func SecurityOptCheck(srv ServiceConfig) *Issue {
i := &Issue{}
if srv.SecOpts == nil {
i.Fatal()
i.Messages = append(i.Messages, "set no-new-privileges=true on the service security_opts")
return i
}
for _, opt := range *srv.SecOpts {
if strings.EqualFold(opt, PRIVILEGE_OPT) {
i.Passed()
return i
}
}
i.Fatal()
i.Messages = append(i.Messages, "set no-new-privileges=true on the container security_opts")
return i
}
func UserCheck(srv ServiceConfig) (*Issue, error) {
i := &Issue{}
if srv.User == nil {
i.Warning()
i.Messages = append(i.Messages, "no user is specified in the compose file")
return i, nil
}
mtch, err := regexp.Match("[0-9].*:[0-9].*", []byte(*srv.User))
if err != nil {
return nil, err
}
if mtch {
if strings.EqualFold(ROOT_USER_GROUP, *srv.User) {
i.Fatal()
i.Messages = append(i.Messages, "a root user set in the compose file")
} else {
i.Passed()
}
return i, nil
} else {
users := strings.Split(*srv.User, ":")
if len(users) != 2 {
i.Fatal()
i.Messages = append(i.Messages, "user value seems misconfigured, check compose file")
return i, nil
}
//shoutouts and/or blame should go to: https://regex101.com/ -- seems like a cool tool, but time will tell
leftUser, err := regexp.Match(`(?m)\${{\s*(vars|secrets)\..*}}`, []byte(users[0]))
if err != nil {
return nil, err
}
rightUser, err := regexp.Match(`(?m)\${{\s*(vars|secrets)\..*}}`, []byte(users[1]))
if err != nil {
return nil, err
}
if leftUser && rightUser {
i.Passed()
return i, nil
}
if leftUser {
if strings.EqualFold(users[1], ROOT_GROUP) {
i.Warning()
i.Messages = append(i.Messages, "a root user is present in configuration")
} else {
i.Passed()
}
return i, nil
}
if rightUser {
if strings.EqualFold(users[0], ROOT_USER) {
i.Warning()
i.Messages = append(i.Messages, "a root user is present in configuration")
} else {
i.Passed()
}
return i, nil
}
}
i.Fatal()
i.Messages = append(i.Messages, "unable to parse user configuration")
return i, nil
}
// func PortCheck(srv ServiceConfig) (*Issue, error) {
// i := &Issue{}
// if srv.Ports == nil {
// i.Passed()
// return i, nil
// }
// 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
// // }
// }