-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>
- Loading branch information
Showing
2 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
// Copyright 2024 The OWASP Coraza contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package main | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
_ "embed" | ||
"fmt" | ||
"go/ast" | ||
"go/parser" | ||
"go/token" | ||
"html" | ||
"html/template" | ||
"log" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type Page struct { | ||
LastModification string | ||
Actions []Action | ||
} | ||
|
||
type Action struct { | ||
Name string | ||
ActionGroup string | ||
Description string | ||
Example string | ||
Phases string | ||
} | ||
|
||
//go:embed template.md | ||
var contentTemplate string | ||
|
||
const dstFile = "./content/docs/seclang/actions.md" | ||
|
||
func main() { | ||
tmpl, err := template.New("action").Parse(contentTemplate) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
var files []string | ||
|
||
root := path.Join("../coraza", "/internal/actions") | ||
|
||
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
fmt.Println(err) | ||
return nil | ||
} | ||
|
||
// get all files that are not test files | ||
if !info.IsDir() && !strings.HasSuffix(info.Name(), "_test.go") && info.Name() != "actions.go" { | ||
files = append(files, path) | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
dstf, err := os.Create(dstFile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer dstf.Close() | ||
|
||
page := Page{ | ||
LastModification: time.Now().Format(time.RFC3339), | ||
} | ||
|
||
for _, file := range files { | ||
page = getActionFromFile(file, page) | ||
} | ||
|
||
sort.Slice(page.Actions, func(i, j int) bool { | ||
return page.Actions[i].Name < page.Actions[j].Name | ||
}) | ||
|
||
content := bytes.Buffer{} | ||
err = tmpl.Execute(&content, page) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
_, err = dstf.WriteString(html.UnescapeString(content.String())) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func getActionFromFile(file string, page Page) Page { | ||
src, err := os.ReadFile(file) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
fSet := token.NewFileSet() | ||
f, err := parser.ParseFile(fSet, file, src, parser.ParseComments) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
actionDoc := "" | ||
ast.Inspect(f, func(n ast.Node) bool { | ||
switch ty := n.(type) { | ||
case *ast.GenDecl: | ||
if ty.Doc.Text() != "" { | ||
actionDoc += ty.Doc.Text() | ||
} | ||
case *ast.TypeSpec: | ||
typeName := ty.Name.String() | ||
if !strings.HasSuffix(typeName, "Fn") { | ||
return true | ||
} | ||
if len(typeName) < 3 { | ||
return true | ||
} | ||
|
||
actionName := typeName[0 : len(typeName)-2] | ||
page.Actions = append(page.Actions, parseAction(actionName, actionDoc)) | ||
} | ||
return true | ||
}) | ||
return page | ||
} | ||
|
||
func parseAction(name string, doc string) Action { | ||
var key string | ||
var value string | ||
var ok bool | ||
|
||
d := Action{ | ||
Name: name, | ||
} | ||
|
||
fieldAppenders := map[string]func(d *Action, value string){ | ||
"Description": func(a *Action, value string) { d.Description += value }, | ||
"Action Group": func(a *Action, value string) { d.ActionGroup += value }, | ||
"Example": func(a *Action, value string) { d.Example += value }, | ||
"Processing Phases": func(a *Action, value string) { d.Phases += value }, | ||
} | ||
|
||
previousKey := "" | ||
scanner := bufio.NewScanner(strings.NewReader(doc)) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
if len(strings.TrimSpace(line)) == 0 { | ||
continue | ||
} | ||
|
||
// There are two types of comments. One is a key-value pair, the other is a continuation of the previous key | ||
// E.g. | ||
// Action Group: Non-disruptive <= first one, key value pair | ||
// Example: <= second one, key in a line, value in the next lines | ||
// This action is used to generate a response. | ||
// | ||
if strings.HasSuffix(line, ":") { | ||
key = line[:len(line)-1] | ||
value = "" | ||
} else { | ||
key, value, ok = strings.Cut(line, ": ") | ||
if !ok { | ||
key = previousKey | ||
value = " " + line | ||
} | ||
} | ||
|
||
if fn, ok := fieldAppenders[key]; ok { | ||
fn(&d, value) | ||
previousKey = key | ||
} else if previousKey != "" { | ||
fieldAppenders[previousKey](&d, value) | ||
} else { | ||
log.Fatalf("unknown field %q", key) | ||
} | ||
} | ||
return d | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
title: "Actions" | ||
description: "Actions available in Coraza" | ||
lead: "The action of a rule defines how to handle HTTP requests that have matched one or more rule conditions." | ||
date: 2020-10-06T08:48:57+00:00 | ||
lastmod: "{{ .LastModification }}" | ||
draft: false | ||
images: [] | ||
menu: | ||
docs: | ||
parent: "seclang" | ||
weight: 100 | ||
toc: true | ||
--- | ||
|
||
[//]: <> (This file is generated by tools/actionsgen. DO NOT EDIT.) | ||
|
||
Actions are defined as part of a `SecRule` or as parameter for `SecAction` or `SecDefaultAction`. A rule can have no or serveral actions which need to be separated by a comma. | ||
|
||
Actions can be categorized by how they affect overall processing: | ||
|
||
* **Disruptive actions** - Cause Coraza to do something. In many cases something means block transaction, but not in all. For example, the allow action is classified as a disruptive action, but it does the opposite of blocking. There can only be one disruptive action per rule (if there are multiple disruptive actions present, or inherited, only the last one will take effect), or rule chain (in a chain, a disruptive action can only appear in the first rule). | ||
{{"{{"}}< alert icon="👉" >{{"}}"}} | ||
Disruptive actions will NOT be executed if the `SecRuleEngine` is set to `DetectionOnly`. If you are creating exception/allowlisting rules that use the allow action, you should also add the `ctl:ruleEngine=On` action to execute the action. | ||
{{"{{"}}< /alert >{{"}}"}} | ||
* **Non-disruptive actions** - Do something, but that something does not and cannot affect the rule processing flow. Setting a variable, or changing its value is an example of a non-disruptive action. Non-disruptive action can appear in any rule, including each rule belonging to a chain. | ||
* **Flow actions** - These actions affect the rule flow (for example skip or skipAfter). | ||
* **Meta-data actions** - used to provide more information about rules. Examples include id, rev, severity and msg. | ||
* **Data actions** - Not really actions, these are mere containers that hold data used by other actions. For example, the status action holds the status that will be used for blocking (if it takes place). | ||
|
||
{{ range .Actions }} | ||
## {{ .Name }} | ||
|
||
**Description**: {{ .Description }} | ||
|
||
**Action Group**: {{ .ActionGroup }} | ||
|
||
{{ if .Phases }} | ||
**Processing Phases**: {{ .Phases }} | ||
{{ end }} | ||
|
||
**Example**: | ||
{{ .Example }} | ||
{{ end }} |