Cybersecurity-Projects/PROJECTS/intermediate/docker-security-audit/internal/parser/visitor.go

158 lines
3.6 KiB
Go

/*
CarterPerez-dev | 2026
visitor.go
Rule visitor infrastructure for applying security rules against parsed
ASTs
RuleVisitor accumulates findings by running a list of Rule
implementations against a Dockerfile or Compose AST. BaseRule,
DockerfileRule, ComposeRule, and MultiRule are composable building
blocks for implementing the Rule interface without boilerplate.
Key exports:
RuleVisitor - runs rules and accumulates findings
Rule - interface with ID() and Check(ctx *RuleContext)
BaseRule, DockerfileRule, ComposeRule, MultiRule - embeddable rule types
RuleContext - holds the parsed AST and target passed to each check
Connects to:
dockerfile.go - visits DockerfileAST via VisitDockerfile
compose.go - visits ComposeFile via VisitCompose
finding.go - produces finding.Collection
*/
package parser
import (
"github.com/CarterPerez-dev/docksec/internal/finding"
)
type RuleVisitor struct {
Target finding.Target
Findings finding.Collection
rules []Rule
}
type Rule interface {
ID() string
Check(ctx *RuleContext) []*finding.Finding
}
type RuleContext struct {
Target finding.Target
Dockerfile *DockerfileAST
ComposeFile *ComposeFile
}
func NewRuleVisitor(target finding.Target, rules ...Rule) *RuleVisitor {
return &RuleVisitor{
Target: target,
rules: rules,
}
}
func (v *RuleVisitor) VisitDockerfile(ast *DockerfileAST) {
ctx := &RuleContext{
Target: v.Target,
Dockerfile: ast,
}
for _, rule := range v.rules {
findings := rule.Check(ctx)
v.Findings = append(v.Findings, findings...)
}
}
func (v *RuleVisitor) VisitCompose(cf *ComposeFile) {
ctx := &RuleContext{
Target: v.Target,
ComposeFile: cf,
}
for _, rule := range v.rules {
findings := rule.Check(ctx)
v.Findings = append(v.Findings, findings...)
}
}
func (v *RuleVisitor) VisitCommand(cmd Command) {
}
func (v *RuleVisitor) VisitService(name string, svc *Service) {
}
func (v *RuleVisitor) Results() finding.Collection {
return v.Findings
}
type BaseRule struct {
RuleID string
Title string
Severity finding.Severity
Category string
Description string
Remediation string
References []string
}
func (r *BaseRule) ID() string {
return r.RuleID
}
func (r *BaseRule) NewFinding(target finding.Target) *finding.Finding {
return finding.New(r.RuleID, r.Title, r.Severity, target).
WithDescription(r.Description).
WithCategory(r.Category).
WithRemediation(r.Remediation).
WithReferences(r.References...)
}
type DockerfileRule struct {
BaseRule
CheckFunc func(ast *DockerfileAST, target finding.Target) []*finding.Finding
}
func (r *DockerfileRule) Check(ctx *RuleContext) []*finding.Finding {
if ctx.Dockerfile == nil {
return nil
}
return r.CheckFunc(ctx.Dockerfile, ctx.Target)
}
type ComposeRule struct {
BaseRule
CheckFunc func(cf *ComposeFile, target finding.Target) []*finding.Finding
}
func (r *ComposeRule) Check(ctx *RuleContext) []*finding.Finding {
if ctx.ComposeFile == nil {
return nil
}
return r.CheckFunc(ctx.ComposeFile, ctx.Target)
}
type MultiRule struct {
BaseRule
DockerfileCheck func(ast *DockerfileAST, target finding.Target) []*finding.Finding
ComposeCheck func(cf *ComposeFile, target finding.Target) []*finding.Finding
}
func (r *MultiRule) Check(ctx *RuleContext) []*finding.Finding {
var findings []*finding.Finding
if ctx.Dockerfile != nil && r.DockerfileCheck != nil {
findings = append(
findings,
r.DockerfileCheck(ctx.Dockerfile, ctx.Target)...)
}
if ctx.ComposeFile != nil && r.ComposeCheck != nil {
findings = append(
findings,
r.ComposeCheck(ctx.ComposeFile, ctx.Target)...)
}
return findings
}