fixing regex, ready for tests
This commit is contained in:
@@ -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
26
compose/compose.go
Normal 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
30
issue/issue.go
Normal 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
146
main.go
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user