Skip to content

Commit 6ad4235

Browse files
authored
Merge pull request #35 from coreruleset/feat/default-config
feat: default global config
2 parents 31b5d17 + 61bd2eb commit 6ad4235

15 files changed

+201
-150
lines changed

crslang_test.go

Lines changed: 12 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,19 @@ import (
44
"os"
55
"testing"
66

7-
"github.com/antlr4-go/antlr/v4"
8-
"github.com/coreruleset/crslang/listener"
97
"github.com/coreruleset/crslang/types"
10-
"github.com/coreruleset/seclang_parser/parser"
118
"go.yaml.in/yaml/v4"
129
)
1310

14-
var testFiles = []string{
15-
"testdata/crs-setup.conf",
16-
"testdata/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf",
17-
"testdata/crs/REQUEST-901-INITIALIZATION.conf",
18-
"testdata/crs/REQUEST-905-COMMON-EXCEPTIONS.conf",
19-
"testdata/crs/REQUEST-911-METHOD-ENFORCEMENT.conf",
20-
"testdata/crs/REQUEST-913-SCANNER-DETECTION.conf",
21-
"testdata/crs/REQUEST-920-PROTOCOL-ENFORCEMENT.conf",
22-
"testdata/crs/REQUEST-921-PROTOCOL-ATTACK.conf",
23-
"testdata/crs/REQUEST-922-MULTIPART-ATTACK.conf",
24-
"testdata/crs/REQUEST-930-APPLICATION-ATTACK-LFI.conf",
25-
"testdata/crs/REQUEST-931-APPLICATION-ATTACK-RFI.conf",
26-
"testdata/crs/REQUEST-932-APPLICATION-ATTACK-RCE.conf",
27-
"testdata/crs/REQUEST-933-APPLICATION-ATTACK-PHP.conf",
28-
"testdata/crs/REQUEST-934-APPLICATION-ATTACK-GENERIC.conf",
29-
"testdata/crs/REQUEST-941-APPLICATION-ATTACK-XSS.conf",
30-
"testdata/crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
31-
"testdata/crs/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf",
32-
"testdata/crs/REQUEST-944-APPLICATION-ATTACK-JAVA.conf",
33-
"testdata/crs/REQUEST-949-BLOCKING-EVALUATION.conf",
34-
"testdata/crs/RESPONSE-950-DATA-LEAKAGES.conf",
35-
"testdata/crs/RESPONSE-951-DATA-LEAKAGES-SQL.conf",
36-
"testdata/crs/RESPONSE-952-DATA-LEAKAGES-JAVA.conf",
37-
"testdata/crs/RESPONSE-953-DATA-LEAKAGES-PHP.conf",
38-
"testdata/crs/RESPONSE-954-DATA-LEAKAGES-IIS.conf",
39-
"testdata/crs/RESPONSE-959-BLOCKING-EVALUATION.conf",
40-
"testdata/crs/RESPONSE-980-CORRELATION.conf",
41-
}
11+
var testFilepath = "testdata/crs/"
4212

4313
func TestLoadCRS(t *testing.T) {
44-
resultConfigs := []types.DirectiveList{}
45-
for _, file := range testFiles {
46-
input, err := antlr.NewFileStream(file)
47-
if err != nil {
48-
panic("Error reading file" + file)
49-
}
50-
lexer := parser.NewSecLangLexer(input)
51-
stream := antlr.NewCommonTokenStream(lexer, 0)
52-
p := parser.NewSecLangParser(stream)
53-
start := p.Configuration()
54-
var seclangListener listener.ExtendedSeclangParserListener
55-
antlr.ParseTreeWalkerDefault.Walk(&seclangListener, start)
56-
resultConfigs = append(resultConfigs, seclangListener.ConfigurationList.DirectiveList...)
57-
}
58-
configList := types.ConfigurationList{DirectiveList: resultConfigs}
5914

60-
configListWithConditions := types.ToDirectiveWithConditions(configList)
15+
configList := LoadSeclang(testFilepath)
16+
17+
configList = *ToCRSLang(configList)
6118

62-
yamlFile, err := yaml.Marshal(configListWithConditions.DirectiveList)
19+
yamlFile, err := yaml.Marshal(configList)
6320
if err != nil {
6421
t.Errorf("Error marshalling yaml: %v", err)
6522
}
@@ -68,12 +25,8 @@ func TestLoadCRS(t *testing.T) {
6825

6926
defer os.Remove("tmp_crslang.yaml")
7027

71-
if err != nil {
72-
t.Errorf("Error writing file: %v", err)
73-
}
74-
7528
loadedConfigList := types.LoadDirectivesWithConditionsFromFile("tmp_crslang.yaml")
76-
yamlLoadedFile, err := yaml.Marshal(loadedConfigList.DirectiveList)
29+
yamlLoadedFile, err := yaml.Marshal(loadedConfigList)
7730
if err != nil {
7831
t.Errorf("Error writing file: %v", err)
7932
}
@@ -84,32 +37,16 @@ func TestLoadCRS(t *testing.T) {
8437
}
8538

8639
func TestFromCRSLangToSeclang(t *testing.T) {
87-
resultConfigs := []types.DirectiveList{}
88-
for _, file := range testFiles {
89-
input, err := antlr.NewFileStream(file)
90-
if err != nil {
91-
panic("Error reading file" + file)
92-
}
93-
lexer := parser.NewSecLangLexer(input)
94-
stream := antlr.NewCommonTokenStream(lexer, 0)
95-
p := parser.NewSecLangParser(stream)
96-
start := p.Configuration()
97-
var seclangListener listener.ExtendedSeclangParserListener
98-
antlr.ParseTreeWalkerDefault.Walk(&seclangListener, start)
99-
resultConfigs = append(resultConfigs, seclangListener.ConfigurationList.DirectiveList...)
100-
}
101-
configList := types.ConfigurationList{DirectiveList: resultConfigs}
40+
configList := LoadSeclang(testFilepath)
10241

10342
seclangDirectives := types.ToSeclang(configList)
10443

105-
configListWithConditions := types.ToDirectiveWithConditions(configList)
106-
107-
configListFromConditions := types.FromCRSLangToUnformattedDirectives(*configListWithConditions)
108-
109-
seclangDirectivesFromConditions := types.ToSeclang(*configListFromConditions)
44+
crslangConfigList := ToCRSLang(configList)
45+
unformattedDirectives := types.FromCRSLangToUnformattedDirectives(*crslangConfigList)
46+
seclangDirectivesFromConditions := types.ToSeclang(*unformattedDirectives)
11047

111-
if seclangDirectives != seclangDirectivesFromConditions {
112-
t.Errorf("Error in CRSLang to Seclang directives convertion. Expected length: %v, got: %v", len(seclangDirectives), len(seclangDirectivesFromConditions))
48+
if len(seclangDirectives) != len(seclangDirectivesFromConditions) {
49+
t.Errorf("Error in CRSLang to Seclang directives conversion. Expected length: %v, got: %v", len(seclangDirectives), len(seclangDirectivesFromConditions))
11350
}
11451

11552
}

main.go

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -47,38 +47,15 @@ Flags:
4747
}
4848

4949
if !*toSeclang {
50-
resultConfigs := []types.DirectiveList{}
51-
filepath.Walk(pathArg, func(path string, info os.FileInfo, err error) error {
52-
if err != nil {
53-
return err
54-
}
55-
if !info.IsDir() && filepath.Ext(info.Name()) == ".conf" {
56-
input, err := antlr.NewFileStream(path)
57-
if err != nil {
58-
panic("Error reading file" + path)
59-
}
60-
lexer := parser.NewSecLangLexer(input)
61-
stream := antlr.NewCommonTokenStream(lexer, 0)
62-
p := parser.NewSecLangParser(stream)
63-
start := p.Configuration()
64-
var seclangListener listener.ExtendedSeclangParserListener
65-
antlr.ParseTreeWalkerDefault.Walk(&seclangListener, start)
66-
for i := range seclangListener.ConfigurationList.DirectiveList {
67-
seclangListener.ConfigurationList.DirectiveList[i].Id = strings.TrimSuffix(filepath.Base(info.Name()), filepath.Ext(info.Name()))
68-
if len(seclangListener.ConfigurationList.DirectiveList) > 1 {
69-
seclangListener.ConfigurationList.DirectiveList[i].Id += "_" + strconv.Itoa(i+1)
70-
}
71-
}
72-
resultConfigs = append(resultConfigs, seclangListener.ConfigurationList.DirectiveList...)
73-
}
74-
return nil
75-
})
76-
configList := types.ConfigurationList{DirectiveList: resultConfigs}
50+
configList := LoadSeclang(pathArg)
51+
52+
configList = *ToCRSLang(configList)
7753

7854
if *output == "" {
7955
*output = "crslang"
8056
}
81-
err := printCRSLang(configList, *output+".yaml")
57+
58+
err := printYAML(configList, *output+".yaml")
8259
if err != nil {
8360
log.Fatal(err.Error())
8461
}
@@ -89,25 +66,53 @@ Flags:
8966

9067
configList := types.LoadDirectivesWithConditionsFromFile(pathArg)
9168

92-
err := printSeclang(configList, *output)
69+
err := PrintSeclang(configList, *output)
9370
if err != nil {
9471
log.Fatal(err.Error())
9572
}
9673
}
9774
}
9875

99-
// printSeclang writes seclang directives to files specified in directive list ids.
100-
func printSeclang(configList types.ConfigurationList, dir string) error {
101-
unfDirs := types.FromCRSLangToUnformattedDirectives(configList)
102-
103-
for _, dirList := range unfDirs.DirectiveList {
104-
f, err := os.Create(dir + dirList.Id + ".conf")
76+
// LoadSeclang loads seclang directives from an input file or folder and returns a ConfigurationList
77+
// if a folder is specified it loads all .conf files in the folder
78+
func LoadSeclang(input string) types.ConfigurationList {
79+
resultConfigs := []types.DirectiveList{}
80+
filepath.Walk(input, func(path string, info os.FileInfo, err error) error {
10581
if err != nil {
10682
return err
10783
}
108-
seclangDirectives := dirList.ToSeclang()
84+
if ext := filepath.Ext(info.Name()); !info.IsDir() && (ext == ".conf" || (ext == ".example" && filepath.Ext(strings.TrimSuffix(info.Name(), ext)) == ".conf")) {
85+
input, err := antlr.NewFileStream(path)
86+
if err != nil {
87+
panic("Error reading file" + path)
88+
}
89+
lexer := parser.NewSecLangLexer(input)
90+
stream := antlr.NewCommonTokenStream(lexer, 0)
91+
p := parser.NewSecLangParser(stream)
92+
start := p.Configuration()
93+
var seclangListener listener.ExtendedSeclangParserListener
94+
antlr.ParseTreeWalkerDefault.Walk(&seclangListener, start)
95+
for i := range seclangListener.ConfigurationList.DirectiveList {
96+
seclangListener.ConfigurationList.DirectiveList[i].Id = strings.TrimSuffix(filepath.Base(info.Name()), filepath.Ext(info.Name()))
97+
if len(seclangListener.ConfigurationList.DirectiveList) > 1 {
98+
seclangListener.ConfigurationList.DirectiveList[i].Id += "_" + strconv.Itoa(i+1)
99+
}
100+
}
101+
resultConfigs = append(resultConfigs, seclangListener.ConfigurationList.DirectiveList...)
102+
}
103+
return nil
104+
})
105+
configList := types.ConfigurationList{DirectiveList: resultConfigs}
106+
return configList
107+
}
109108

110-
_, err = io.WriteString(f, seclangDirectives)
109+
// PrintSeclang writes seclang directives to files specified in directive list ids.
110+
func PrintSeclang(configList types.ConfigurationList, dir string) error {
111+
unfDirs := types.FromCRSLangToUnformattedDirectives(configList)
112+
113+
for _, dirList := range unfDirs.DirectiveList {
114+
seclangDirectives := dirList.ToSeclang()
115+
err := writeToFile([]byte(seclangDirectives), dir+dirList.Id+".conf")
111116
if err != nil {
112117
return err
113118
}
@@ -116,28 +121,17 @@ func printSeclang(configList types.ConfigurationList, dir string) error {
116121
return nil
117122
}
118123

119-
// printSeclangToFile writes seclang format directives to a file
120-
func printSeclangToFile(configList types.ConfigurationList, filename string) error {
121-
f, err := os.Create(filename)
122-
if err != nil {
123-
return err
124-
}
125-
126-
seclangDirectives := types.ToSeclang(configList)
127-
128-
_, err = io.WriteString(f, seclangDirectives)
129-
if err != nil {
130-
return err
131-
}
124+
// ToCRSLang process previously loaded seclang directives to CRSLang schema directives
125+
func ToCRSLang(configList types.ConfigurationList) *types.ConfigurationList {
126+
configListWithConditions := types.ToDirectiveWithConditions(configList)
132127

133-
return nil
128+
configListWithConditions.ExtractDefaultValues()
129+
return configListWithConditions
134130
}
135131

136-
// printCRSLang writes crslang format directives (directives with conditions) to a file
137-
func printCRSLang(configList types.ConfigurationList, filename string) error {
138-
configListWithConditions := types.ToDirectiveWithConditions(configList)
139-
140-
yamlFile, err := yaml.Marshal(configListWithConditions.DirectiveList)
132+
// printYAML marshal and write structures to a yaml file
133+
func printYAML(input any, filename string) error {
134+
yamlFile, err := yaml.Marshal(input)
141135
if err != nil {
142136
return err
143137
}

testdata/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@
199199
# ctl:ruleRemoveById=920350,\
200200
# ctl:ruleRemoveByTag=attack-disclosure"
201201

202-
SecRule REQUEST_FILENAME "@endsWith wp-login.php" \
203-
"id:1003,\
204-
phase:2,\
205-
pass,\
206-
nolog,\
207-
ctl:ruleRemoveTargetByTag=OWASP_CRS;ARGS:pwd"
202+
# SecRule REQUEST_FILENAME "@endsWith wp-login.php" \
203+
# "id:1003,\
204+
# phase:2,\
205+
# pass,\
206+
# nolog,\
207+
# ctl:ruleRemoveTargetByTag=OWASP_CRS;ARGS:pwd"

types/condition_directives.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ type RuleWithCondition struct {
4747
ChainedRule *RuleWithCondition `yaml:"chainedRule,omitempty"`
4848
}
4949

50-
func (s RuleWithCondition) ToSeclang() string {
50+
func (s *RuleWithCondition) ToSeclang() string {
5151
return "New sec rule with conditions"
5252
}
5353

54+
func (s *RuleWithCondition) GetKind() Kind {
55+
return s.Kind
56+
}
57+
5458
func ToDirectiveWithConditions(configList ConfigurationList) *ConfigurationList {
5559
result := new(ConfigurationList)
5660
for _, config := range configList.DirectiveList {
@@ -81,7 +85,7 @@ func ToDirectiveWithConditions(configList ConfigurationList) *ConfigurationList
8185
return result
8286
}
8387

84-
func RuleToCondition(directive ChainableDirective) RuleWithCondition {
88+
func RuleToCondition(directive ChainableDirective) *RuleWithCondition {
8589
var ruleWithCondition RuleWithCondition
8690
switch directive.(type) {
8791
case *SecRule:
@@ -131,7 +135,7 @@ func RuleToCondition(directive ChainableDirective) RuleWithCondition {
131135
if directive.GetChainedDirective() != nil {
132136
chainedConditionRule := RuleToCondition(directive.GetChainedDirective())
133137
if directive.NonDisruptiveActionsCount() > 0 {
134-
ruleWithCondition.ChainedRule = &chainedConditionRule
138+
ruleWithCondition.ChainedRule = chainedConditionRule
135139
} else {
136140
ruleWithCondition.Conditions = append(ruleWithCondition.Conditions, chainedConditionRule.Conditions...)
137141
ruleWithCondition.Actions.NonDisruptiveActions = chainedConditionRule.Actions.NonDisruptiveActions
@@ -140,7 +144,13 @@ func RuleToCondition(directive ChainableDirective) RuleWithCondition {
140144
}
141145
}
142146
}
143-
return ruleWithCondition
147+
return &ruleWithCondition
148+
}
149+
150+
// configurationYamlLoader is a auxiliary struct to load the whole yaml file
151+
type configurationYamlLoader struct {
152+
Global DefaultConfigs `yaml:"global,omitempty"`
153+
DirectiveList []yamlLoaderConditionRules `yaml:"directivelist,omitempty"`
144154
}
145155

146156
// yamlLoaderConditionRules is a auxiliary struct to load and iterate over the yaml file
@@ -224,8 +234,9 @@ func LoadDirectivesWithConditionsFromFile(filename string) ConfigurationList {
224234

225235
// LoadDirectivesWithConditions loads condition format directives from a yaml file
226236
func LoadDirectivesWithConditions(yamlFile []byte) ConfigurationList {
227-
var configs []yamlLoaderConditionRules
228-
err := yaml.Unmarshal(yamlFile, &configs)
237+
var config configurationYamlLoader
238+
err := yaml.Unmarshal(yamlFile, &config)
239+
configs := config.DirectiveList
229240
if err != nil {
230241
panic(err)
231242
}
@@ -242,7 +253,7 @@ func LoadDirectivesWithConditions(yamlFile []byte) ConfigurationList {
242253
}
243254
resultConfigs = append(resultConfigs, DirectiveList{Id: config.Id, Directives: directives, Marker: config.Marker})
244255
}
245-
return ConfigurationList{DirectiveList: resultConfigs}
256+
return ConfigurationList{Global: config.Global, DirectiveList: resultConfigs}
246257
}
247258

248259
// loadConditionDirective loads the different kind of directives
@@ -324,7 +335,7 @@ func loadConditionDirective(yamlDirective yaml.Node) SeclangDirective {
324335
}
325336

326337
// loadRuleWithConditions loads a rule with conditions in a recursive way
327-
func loadRuleWithConditions(yamlDirective yaml.Node) RuleWithCondition {
338+
func loadRuleWithConditions(yamlDirective yaml.Node) *RuleWithCondition {
328339
rawDirective := []byte{}
329340
var err error
330341

@@ -340,7 +351,7 @@ func loadRuleWithConditions(yamlDirective yaml.Node) RuleWithCondition {
340351
print(string(rawDirective))
341352
panic(err)
342353
}
343-
directive := RuleWithCondition{
354+
directive := &RuleWithCondition{
344355
Kind: RuleKind,
345356
Metadata: loaderDirective.Metadata,
346357
Actions: loaderDirective.Actions,
@@ -351,10 +362,10 @@ func loadRuleWithConditions(yamlDirective yaml.Node) RuleWithCondition {
351362
directive.Conditions = append(directive.Conditions, loadedCondition)
352363
}
353364
}
354-
var loadedChainedRule RuleWithCondition
365+
var loadedChainedRule *RuleWithCondition
355366
if len(loaderDirective.ChainedRule.Content) > 0 {
356367
loadedChainedRule = loadRuleWithConditions(loaderDirective.ChainedRule)
357-
directive.ChainedRule = &loadedChainedRule
368+
directive.ChainedRule = loadedChainedRule
358369
}
359370
return directive
360371
}
@@ -400,8 +411,15 @@ func FromCRSLangToUnformattedDirectives(configListWrapped ConfigurationList) *Co
400411
directive = directiveWrapped.(CommentDirective).Metadata
401412
case DefaultAction:
402413
directive = directiveWrapped
403-
case RuleWithCondition:
404-
directive = FromConditionToUnmorfattedDirective(directiveWrapped.(RuleWithCondition))
414+
case *RuleWithCondition:
415+
chainableDir := FromConditionToUnmorfattedDirective(*directiveWrapped.(*RuleWithCondition))
416+
if configListWrapped.Global.Version != "" {
417+
chainableDir.GetMetadata().SetVer(configListWrapped.Global.Version)
418+
}
419+
for _, tag := range configListWrapped.Global.Tags {
420+
chainableDir.GetMetadata().AddTag(tag)
421+
}
422+
directive = chainableDir
405423
case ConfigurationDirective:
406424
directive = ConfigurationDirective{
407425
Metadata: directiveWrapped.(ConfigurationDirective).Metadata,

0 commit comments

Comments
 (0)