fixing regex, ready for tests

This commit is contained in:
2025-11-11 13:33:14 -05:00
parent 09b33762ec
commit a0e397a87f
4 changed files with 105 additions and 99 deletions

View File

@@ -6,7 +6,7 @@ inputs:
required: true required: true
default: "compose.yaml" default: "compose.yaml"
ignore: ignore:
description: "checks to ignore" description: "checks to ignore (doesn't work yet)"
required: false required: false
outputs: outputs:
report: report:

26
compose/compose.go Normal file
View File

@@ -0,0 +1,26 @@
package compose
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"`
Privileged *bool `yaml:"privileged"`
}
type Compose struct {
Services map[string]ServiceConfig `yaml:"services"`
}

30
issue/issue.go Normal file
View File

@@ -0,0 +1,30 @@
package issue
const (
FATAL IssueLevel = "FATAL"
WARNING IssueLevel = "WARNING"
PASSED IssueLevel = "PASSED"
)
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
}

146
main.go
View File

@@ -8,69 +8,22 @@ import (
"regexp" "regexp"
"strings" "strings"
"code.jakeyoungdev.com/go/compose-parser/compose"
"code.jakeyoungdev.com/go/compose-parser/issue"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
//shoutouts and/or blame should go to: https://regex101.com/ it helped me construct some unholy regex
const ( const (
PRIVILEGE_OPT = "no-new-privileges=true" PRIVILEGE_OPT = "no-new-privileges=true"
ROOT_USER_GROUP = "1000:1000" ROOT_USER_GROUP = "1000:1000"
ROOT_USER = "1000" ROOT_USER = "1000"
ROOT_GROUP = "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]+)` //this is an insane regex to detect IP:PORT:PORT in port configuration but also supports the ability to detect secrets.* and vars.* from workflows
IM_SO_SORRY = `^(\${{\s*(vars|secrets)\.[[:alnum:]]+\s*}}|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}){1}:(\${{\s*(vars|secrets)\.[[:alnum:]]+\s*}}|[0-9]+){1}:(\${{\s*(vars|secrets)\.[[:alnum:]]+\s*}}|[0-9]+){1}$`
FATAL IssueLevel = "FATAL"
WARNING IssueLevel = "WARNING"
PASSED IssueLevel = "PASSED"
) )
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
}
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"`
Privileged *bool `yaml:"privileged"`
}
type Compose struct {
Services map[string]ServiceConfig `yaml:"services"`
}
func main() { func main() {
filePath := os.Getenv("COMPOSE_FILE_PATH") filePath := os.Getenv("COMPOSE_FILE_PATH")
if filePath == "" { if filePath == "" {
@@ -87,45 +40,20 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
var data Compose var data compose.Compose
err = yaml.Unmarshal(fd, &data) err = yaml.Unmarshal(fd, &data)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// type report struct { 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
// }
type NewReport struct {
Name string Name string
Issues []*Issue Issues []*issue.Issue
} }
var issues []NewReport var issues []report
for name, serviceConf := range data.Services { for name, serviceConf := range data.Services {
rprt := NewReport{ rprt := report{
Name: name, Name: name,
} }
@@ -160,6 +88,11 @@ func main() {
rprt.Issues = append(rprt.Issues, m) rprt.Issues = append(rprt.Issues, m)
} }
n := PrivilegedCheck(serviceConf)
if n != nil {
rprt.Issues = append(rprt.Issues, n)
}
issues = append(issues, rprt) issues = append(issues, rprt)
} }
@@ -171,8 +104,9 @@ func main() {
} }
} }
func ResourceCheck(srv ServiceConfig) *Issue { // ensure cpus and mem_limit are set on the service
i := &Issue{} func ResourceCheck(srv compose.ServiceConfig) *issue.Issue {
i := &issue.Issue{}
if srv.Cpus == nil { if srv.Cpus == nil {
i.Warning() i.Warning()
i.Messages = append(i.Messages, "there is no cpu limit set for the service") i.Messages = append(i.Messages, "there is no cpu limit set for the service")
@@ -193,8 +127,9 @@ func ResourceCheck(srv ServiceConfig) *Issue {
return i return i
} }
func PrivilegedCheck(srv ServiceConfig) *Issue { // make sure service is not ran as privileged
i := &Issue{} func PrivilegedCheck(srv compose.ServiceConfig) *issue.Issue {
i := &issue.Issue{}
if srv.Privileged == nil { if srv.Privileged == nil {
i.Passed() i.Passed()
i.Messages = append(i.Messages, "no privileged flag set") i.Messages = append(i.Messages, "no privileged flag set")
@@ -212,8 +147,9 @@ func PrivilegedCheck(srv ServiceConfig) *Issue {
return i return i
} }
func SecurityOptCheck(srv ServiceConfig) *Issue { // checks for specific security options
i := &Issue{} func SecurityOptCheck(srv compose.ServiceConfig) *issue.Issue {
i := &issue.Issue{}
if srv.SecOpts == nil { if srv.SecOpts == nil {
i.Fatal() i.Fatal()
i.Messages = append(i.Messages, "set no-new-privileges=true on the service security_opts") i.Messages = append(i.Messages, "set no-new-privileges=true on the service security_opts")
@@ -233,10 +169,11 @@ func SecurityOptCheck(srv ServiceConfig) *Issue {
return i return i
} }
func PortCheck(srv ServiceConfig) ([]*Issue, error) { // checking port to ensure it is behind a reverse proxy
il := []*Issue{} func PortCheck(srv compose.ServiceConfig) ([]*issue.Issue, error) {
il := []*issue.Issue{}
if srv.Ports == nil { if srv.Ports == nil {
i := &Issue{} i := &issue.Issue{}
i.Passed() i.Passed()
i.Messages = append(i.Messages, "no ports exposed") i.Messages = append(i.Messages, "no ports exposed")
il = append(il, i) il = append(il, i)
@@ -244,7 +181,7 @@ func PortCheck(srv ServiceConfig) ([]*Issue, error) {
} }
for _, prt := range *srv.Ports { for _, prt := range *srv.Ports {
i := &Issue{} i := &issue.Issue{}
m, err := regexp.Match(`^[0-9]*:[0-9]*`, []byte(prt)) m, err := regexp.Match(`^[0-9]*:[0-9]*`, []byte(prt))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -257,6 +194,18 @@ func PortCheck(srv ServiceConfig) ([]*Issue, error) {
continue continue
} }
ms, err := regexp.Match(`^\${{\s*(vars|secrets)\.[[:alnum:]]+\s*}}{1}:\${{\s*(vars|secrets)\.[[:alnum:]]+\s*}}{1}$`, []byte(prt))
if err != nil {
log.Fatal(err)
}
if ms {
i.Warning()
i.Messages = append(i.Messages, fmt.Sprintf("%s could be exposed", prt))
il = append(il, i)
continue
}
m2, err := regexp.Match(IM_SO_SORRY, []byte(prt)) m2, err := regexp.Match(IM_SO_SORRY, []byte(prt))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -278,8 +227,9 @@ func PortCheck(srv ServiceConfig) ([]*Issue, error) {
return il, nil return il, nil
} }
func UserCheck(srv ServiceConfig) (*Issue, error) { // ensures the user value is set and nonroot in the service
i := &Issue{} func UserCheck(srv compose.ServiceConfig) (*issue.Issue, error) {
i := &issue.Issue{}
if srv.User == nil { if srv.User == nil {
i.Warning() i.Warning()
i.Messages = append(i.Messages, "no user is specified in the compose file") i.Messages = append(i.Messages, "no user is specified in the compose file")
@@ -307,7 +257,6 @@ func UserCheck(srv ServiceConfig) (*Issue, error) {
i.Messages = append(i.Messages, "user value seems misconfigured, check compose file") i.Messages = append(i.Messages, "user value seems misconfigured, check compose file")
return i, nil 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])) leftUser, err := regexp.Match(`(?m)\${{\s*(vars|secrets)\..*}}`, []byte(users[0]))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -351,8 +300,9 @@ func UserCheck(srv ServiceConfig) (*Issue, error) {
return i, nil return i, nil
} }
func PolicyCheck(srv ServiceConfig) *Issue { // ensures a restart policy is set in the service configuration with at least max_attempts and restart condition set
i := &Issue{} func PolicyCheck(srv compose.ServiceConfig) *issue.Issue {
i := &issue.Issue{}
if srv.Deploy != nil { if srv.Deploy != nil {
if srv.Deploy.Policy != nil { if srv.Deploy.Policy != nil {
if srv.Deploy.Policy.Condition != nil { if srv.Deploy.Policy.Condition != nil {