mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2025-01-18 08:58:15 +01:00
feat(aa): add ParseRules: parse apparmor profile rules by paragraphs
This commit is contained in:
parent
cdab2588aa
commit
ac9d6d859f
2 changed files with 523 additions and 80 deletions
|
@ -82,6 +82,26 @@ func validateValues(kind Kind, key string, values []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tokenToSlice(token string) []string {
|
||||||
|
res := []string{}
|
||||||
|
token = strings.Trim(token, "()\n")
|
||||||
|
if strings.ContainsAny(token, ", ") {
|
||||||
|
var sep string
|
||||||
|
switch {
|
||||||
|
case strings.Contains(token, ","):
|
||||||
|
sep = ","
|
||||||
|
case strings.Contains(token, " "):
|
||||||
|
sep = " "
|
||||||
|
}
|
||||||
|
for _, v := range strings.Split(token, sep) {
|
||||||
|
res = append(res, strings.Trim(v, " "))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = append(res, token)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to convert a string to a slice of rule values according to
|
// Helper function to convert a string to a slice of rule values according to
|
||||||
// the rule requirements as defined in the requirements map.
|
// the rule requirements as defined in the requirements map.
|
||||||
func toValues(kind Kind, key string, input string) ([]string, error) {
|
func toValues(kind Kind, key string, input string) ([]string, error) {
|
||||||
|
|
583
pkg/aa/parse.go
583
pkg/aa/parse.go
|
@ -6,11 +6,15 @@ package aa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
tokALLOW = "allow"
|
||||||
|
tokAUDIT = "audit"
|
||||||
|
tokDENY = "deny"
|
||||||
tokARROW = "->"
|
tokARROW = "->"
|
||||||
tokEQUAL = "="
|
tokEQUAL = "="
|
||||||
tokLESS = "<"
|
tokLESS = "<"
|
||||||
|
@ -25,11 +29,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
newRuleMap = map[string]func([]string) (Rule, error){
|
newRuleMap = map[string]func(q Qualifier, r rule) (Rule, error){
|
||||||
COMMENT.Tok(): newComment,
|
|
||||||
ABI.Tok(): newAbi,
|
|
||||||
ALIAS.Tok(): newAlias,
|
|
||||||
INCLUDE.Tok(): newInclude,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tok = map[Kind]string{
|
tok = map[Kind]string{
|
||||||
|
@ -39,8 +39,142 @@ var (
|
||||||
}
|
}
|
||||||
openBlocks = []rune{tokOPENPAREN, tokOPENBRACE, tokOPENBRACKET}
|
openBlocks = []rune{tokOPENPAREN, tokOPENBRACE, tokOPENBRACKET}
|
||||||
closeBlocks = []rune{tokCLOSEPAREN, tokCLOSEBRACE, tokCLOSEBRACKET}
|
closeBlocks = []rune{tokCLOSEPAREN, tokCLOSEBRACE, tokCLOSEBRACKET}
|
||||||
|
|
||||||
|
inHeader = false
|
||||||
|
regParagraph = regexp.MustCompile(`(?s).*?\n\n|$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parse the line rule from a raw string.
|
||||||
|
func parseLineRules(isPreamble bool, input string) (string, Rules, error) {
|
||||||
|
var res Rules
|
||||||
|
var r Rule
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
tmp := strings.TrimLeft(line, "\t ")
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(tmp, COMMENT.Tok()):
|
||||||
|
r, err = newComment(rule{kv{comment: tmp[1:]}})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
res = append(res, r)
|
||||||
|
input = strings.Replace(input, line, "", 1)
|
||||||
|
|
||||||
|
case strings.HasPrefix(tmp, INCLUDE.Tok()):
|
||||||
|
r, err = newInclude(parseRule(line)[1:])
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
res = append(res, r)
|
||||||
|
input = strings.Replace(input, line, "", 1)
|
||||||
|
|
||||||
|
case strings.HasPrefix(tmp, VARIABLE.Tok()) && isPreamble:
|
||||||
|
r, err = newVariableFromRule(parseRule(line))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
res = append(res, r)
|
||||||
|
input = strings.Replace(input, line, "", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the comma rules from a raw string. It splits rules string into tokens
|
||||||
|
// separated by "," but ignore comma inside comments, quotes, brakets, and parentheses.
|
||||||
|
// Warning: the input string should only contain comma rules.
|
||||||
|
// Return a pre-parsed rule struct representation of a profile/block.
|
||||||
|
func parseCommaRules(input string) ([]rule, error) {
|
||||||
|
rules := []rule{}
|
||||||
|
blockStart := 0
|
||||||
|
blockCounter := 0
|
||||||
|
comment := false
|
||||||
|
aare := false
|
||||||
|
canHaveInlineComment := false
|
||||||
|
size := len(input)
|
||||||
|
for idx, r := range input {
|
||||||
|
switch r {
|
||||||
|
case tokOPENBRACE, tokOPENBRACKET, tokOPENPAREN:
|
||||||
|
if !comment {
|
||||||
|
blockCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
case tokCLOSEBRACE, tokCLOSEBRACKET, tokCLOSEPAREN:
|
||||||
|
if !comment {
|
||||||
|
blockCounter--
|
||||||
|
}
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
if !comment && canHaveInlineComment {
|
||||||
|
comment = true
|
||||||
|
blockStart = idx + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
if comment {
|
||||||
|
comment = !comment
|
||||||
|
if canHaveInlineComment {
|
||||||
|
commentRaw := input[blockStart:idx]
|
||||||
|
|
||||||
|
// Inline comments belong to the previous rule (in the same line)
|
||||||
|
lastRule := rules[len(rules)-1]
|
||||||
|
lastRule[len(lastRule)-1].comment = commentRaw
|
||||||
|
|
||||||
|
// Ignore the collected comment for the next rule
|
||||||
|
blockStart = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canHaveInlineComment = false
|
||||||
|
|
||||||
|
case tokCOLON:
|
||||||
|
if blockCounter == 0 && !comment {
|
||||||
|
if idx+1 < size && !strings.ContainsRune(" \n", rune(input[idx+1])) {
|
||||||
|
// Colon in AARE, it is valid, not a separator
|
||||||
|
aare = true
|
||||||
|
}
|
||||||
|
if !aare {
|
||||||
|
ruleRaw := input[blockStart:idx]
|
||||||
|
ruleRaw = strings.Trim(ruleRaw, "\n ")
|
||||||
|
rules = append(rules, parseRule(ruleRaw))
|
||||||
|
blockStart = idx + 1
|
||||||
|
canHaveInlineComment = true
|
||||||
|
}
|
||||||
|
aare = false
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseParagraph(input string) (Rules, error) {
|
||||||
|
// Line rules
|
||||||
|
var raw string
|
||||||
|
raw, res, err := parseLineRules(false, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comma rules
|
||||||
|
rules, err := parseCommaRules(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rrr, err := newRules(rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, rrr...)
|
||||||
|
for _, r := range res {
|
||||||
|
if r.Constraint() == preambleKind {
|
||||||
|
return nil, fmt.Errorf("Rule not allowed in block: %s", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Split a raw input rule string into tokens by space or =, but ignore spaces
|
// Split a raw input rule string into tokens by space or =, but ignore spaces
|
||||||
// within quotes, brakets, or parentheses.
|
// within quotes, brakets, or parentheses.
|
||||||
//
|
//
|
||||||
|
@ -51,14 +185,13 @@ var (
|
||||||
// Returns:
|
// Returns:
|
||||||
//
|
//
|
||||||
// []string{"owner", "@{user_config_dirs}/powerdevilrc{,.@{rand6}}", "rwl", "->", "@{user_config_dirs}/#@{int}"}
|
// []string{"owner", "@{user_config_dirs}/powerdevilrc{,.@{rand6}}", "rwl", "->", "@{user_config_dirs}/#@{int}"}
|
||||||
func tokenize(str string) []string {
|
func tokenizeRule(str string) []string {
|
||||||
var currentToken strings.Builder
|
var currentToken strings.Builder
|
||||||
var isVariable bool
|
isVariable, wasTokPLUS, quoted := false, false, false
|
||||||
var quoted bool
|
|
||||||
|
|
||||||
blockStack := []rune{}
|
blockStack := []rune{}
|
||||||
tokens := make([]string, 0, len(str)/2)
|
tokens := make([]string, 0, len(str)/2)
|
||||||
if len(str) > 2 && str[0:2] == VARIABLE.Tok() {
|
if inHeader && len(str) > 2 && str[0:2] == VARIABLE.Tok() {
|
||||||
isVariable = true
|
isVariable = true
|
||||||
}
|
}
|
||||||
for _, r := range str {
|
for _, r := range str {
|
||||||
|
@ -70,13 +203,18 @@ func tokenize(str string) []string {
|
||||||
currentToken.Reset()
|
currentToken.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
case (r == '=' || r == '+') && len(blockStack) == 0 && !quoted && isVariable:
|
case (r == '+' || r == '=') && len(blockStack) == 0 && !quoted && isVariable:
|
||||||
// Handle variable assignment
|
// Handle variable assignment
|
||||||
if currentToken.Len() != 0 {
|
if currentToken.Len() != 0 {
|
||||||
tokens = append(tokens, currentToken.String())
|
tokens = append(tokens, currentToken.String())
|
||||||
currentToken.Reset()
|
currentToken.Reset()
|
||||||
}
|
}
|
||||||
tokens = append(tokens, string(r))
|
if wasTokPLUS {
|
||||||
|
tokens[len(tokens)-1] = tokPLUS + tokEQUAL
|
||||||
|
} else {
|
||||||
|
tokens = append(tokens, string(r))
|
||||||
|
}
|
||||||
|
wasTokPLUS = (r == '+')
|
||||||
|
|
||||||
case r == '"' && len(blockStack) == 0:
|
case r == '"' && len(blockStack) == 0:
|
||||||
quoted = !quoted
|
quoted = !quoted
|
||||||
|
@ -104,39 +242,246 @@ func tokenize(str string) []string {
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
func tokenToSlice(token string) []string {
|
// Parse a string into a rule struct.
|
||||||
res := []string{}
|
// The input string must be a tokenised raw line rule (using parseCommaRules or
|
||||||
token = strings.Trim(token, "()\n")
|
// parseBlock).
|
||||||
if strings.ContainsAny(token, ", ") {
|
// Example:
|
||||||
var sep string
|
//
|
||||||
|
// `unix (send receive) type=stream addr="@/tmp/.ICE[0-9]-unix/19 5" peer=(label=gnome-shell, addr=none)`
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// rule{
|
||||||
|
// {key: "unix"}, {key: "send"}, {key: "receive"},
|
||||||
|
// {key: "type", values: rule{{key: "stream"}}},
|
||||||
|
// {key: "addr", values: rule{
|
||||||
|
// {key: `"@/tmp/.ICE[0-9]*-unix/19 5"`},
|
||||||
|
// }},
|
||||||
|
// {key: "peer", values: rule{
|
||||||
|
// {key: "label", values: rule{{key: `"@{p_systemd}"`}}},
|
||||||
|
// {key: "addr", values: rule{{key: "none"}}},
|
||||||
|
// }},
|
||||||
|
// },
|
||||||
|
func parseRule(str string) rule {
|
||||||
|
res := make(rule, 0, len(str)/2)
|
||||||
|
tokens := tokenizeRule(str)
|
||||||
|
|
||||||
|
inAare := false
|
||||||
|
if len(tokens) > 0 && (isAARE(tokens[0]) || tokens[0] == tokOWNER) {
|
||||||
|
inAare = true
|
||||||
|
}
|
||||||
|
for idx, token := range tokens {
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(token, ","):
|
case token == tokEQUAL, token == tokPLUS+tokEQUAL, token == tokLESS+tokEQUAL: // Variable & Rlimit
|
||||||
sep = ","
|
res = append(res, kv{key: token})
|
||||||
case strings.Contains(token, " "):
|
|
||||||
sep = " "
|
case strings.Contains(token, "=") && !inAare: // Map
|
||||||
|
items := strings.SplitN(token, "=", 2)
|
||||||
|
key := items[0]
|
||||||
|
if len(items) > 1 {
|
||||||
|
values := strings.Trim(items[1], ",")
|
||||||
|
if strings.Contains(values, "=") || !strings.ContainsAny(values, ", ") {
|
||||||
|
values = strings.Trim(values, "()\n")
|
||||||
|
}
|
||||||
|
res = append(res, kv{key: key, values: parseRule(values)})
|
||||||
|
} else {
|
||||||
|
res = append(res, kv{key: key})
|
||||||
|
}
|
||||||
|
|
||||||
|
case strings.Contains(token, "(") && !inAare: // List
|
||||||
|
token = strings.Trim(token, "()\n")
|
||||||
|
var sep string
|
||||||
|
switch {
|
||||||
|
case strings.Contains(token, ","):
|
||||||
|
sep = ","
|
||||||
|
case strings.Contains(token, " "):
|
||||||
|
sep = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
var values rule
|
||||||
|
if sep == "" {
|
||||||
|
values = append(values, kv{key: token})
|
||||||
|
} else {
|
||||||
|
for _, v := range strings.Split(token, sep) {
|
||||||
|
values = append(values, kv{
|
||||||
|
key: strings.Trim(v, " "),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, values...)
|
||||||
|
|
||||||
|
case strings.HasPrefix(token, COMMENT.Tok()): // Comment
|
||||||
|
if idx > 0 && idx < len(tokens)-1 {
|
||||||
|
res[len(res)-1].comment = " " + strings.Join(tokens[idx+1:], " ")
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // Single value
|
||||||
|
token = strings.Trim(token, "\n")
|
||||||
|
res = append(res, kv{key: token})
|
||||||
}
|
}
|
||||||
for _, v := range strings.Split(token, sep) {
|
|
||||||
res = append(res, strings.Trim(v, " "))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res = append(res, token)
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func tokensStripComment(tokens []string) []string {
|
// Intermediate token for the representation of a rule. All comma and line
|
||||||
|
// rules are parsed into this structure. Then, they are converted into the actual
|
||||||
|
// rule struct using basic constructor functions.
|
||||||
|
type rule []kv
|
||||||
|
|
||||||
|
type kv struct {
|
||||||
|
key string
|
||||||
|
values rule
|
||||||
|
comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return the value of a key from a rule.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `include <tunables/global>`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// Get(0): "include"
|
||||||
|
// Get(1): "<tunables/global>"
|
||||||
|
func (r rule) Get(idx int) string {
|
||||||
|
return r[idx].key
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString return string representation of a rule.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `profile foo @{exec_path} flags=(complain attach_disconnected)`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// "profile foo @{exec_path}"
|
||||||
|
func (r rule) GetString() string {
|
||||||
|
return strings.Join(r.GetSlice(), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlice return a slice of all non map value of a rule.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `profile foo @{exec_path} flags=(complain attach_disconnected)`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// []string{"profile", "foo", "@{exec_path}"}
|
||||||
|
func (r rule) GetSlice() []string {
|
||||||
res := []string{}
|
res := []string{}
|
||||||
for _, v := range tokens {
|
for _, kv := range r {
|
||||||
if v == COMMENT.Tok() {
|
if kv.values == nil {
|
||||||
break
|
res = append(res, kv.key)
|
||||||
}
|
}
|
||||||
res = append(res, v)
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAsMap return a map of slice of all map value of a rule.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `profile foo @{exec_path} flags=(complain attach_disconnected)`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// map[string]string{"flags": {"complain", "attach_disconnected"}}
|
||||||
|
func (r rule) GetAsMap() map[string][]string {
|
||||||
|
res := map[string][]string{}
|
||||||
|
for _, kv := range r {
|
||||||
|
if kv.values != nil {
|
||||||
|
res[kv.key] = kv.values.GetSlice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValues return the values from a key.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `dbus receive peer=(name=:1.3, label=power-profiles-daemon)`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// GetValues("peer"):
|
||||||
|
// rule{
|
||||||
|
// {key: "name", values: rule{{Key: ":1.3"}}},
|
||||||
|
// {key: "label", values: rule{{Key: "power-profiles-daemon"}}},
|
||||||
|
// }},
|
||||||
|
func (r rule) GetValues(key string) rule {
|
||||||
|
for _, kv := range r {
|
||||||
|
if kv.key == key {
|
||||||
|
return kv.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValuesAsSlice return the values from a key as a slice.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `mount options=(rw silent rprivate) -> /oldroot/`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// GetValuesAsSlice("options"):
|
||||||
|
// []string{"rw", "silent", "rprivate"}
|
||||||
|
func (r rule) GetValuesAsSlice(key string) []string {
|
||||||
|
return r.GetValues(key).GetSlice()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValuesAsString return the values from a key as a string.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// `signal (receive) set=(term) peer=at-spi-bus-launcher`
|
||||||
|
//
|
||||||
|
// Gives:
|
||||||
|
//
|
||||||
|
// GetValuesAsString("peer"): "at-spi-bus-launcher"
|
||||||
|
func (r rule) GetValuesAsString(key string) string {
|
||||||
|
return r.GetValues(key).GetString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String return a generic representation of a rule.
|
||||||
|
func (r rule) String() string {
|
||||||
|
var res strings.Builder
|
||||||
|
for _, kv := range r {
|
||||||
|
if kv.values == nil {
|
||||||
|
if res.Len() > 0 {
|
||||||
|
res.WriteString(" ")
|
||||||
|
}
|
||||||
|
res.WriteString(kv.key)
|
||||||
|
} else {
|
||||||
|
res.WriteString(" " + kv.key)
|
||||||
|
v := strings.TrimLeft(kv.values.String(), " ")
|
||||||
|
if strings.Contains(v, " ") || strings.Contains(v, "=") {
|
||||||
|
res.WriteString("=(" + v + ")")
|
||||||
|
} else {
|
||||||
|
res.WriteString("=" + v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kv.comment != "" {
|
||||||
|
res.WriteString(COMMENT.Tok() + " " + kv.comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAARE(str string) bool {
|
||||||
|
return strings.HasPrefix(str, "@") ||
|
||||||
|
strings.HasPrefix(str, "/") ||
|
||||||
|
strings.HasPrefix(str, "\"")
|
||||||
|
}
|
||||||
|
|
||||||
// Convert a slice of internal rules to a slice of ApparmorRule.
|
// Convert a slice of internal rules to a slice of ApparmorRule.
|
||||||
func newRules(rules [][]string) (Rules, error) {
|
func newRules(rules []rule) (Rules, error) {
|
||||||
var err error
|
var err error
|
||||||
var r Rule
|
var r Rule
|
||||||
res := make(Rules, 0, len(rules))
|
res := make(Rules, 0, len(rules))
|
||||||
|
@ -146,100 +491,178 @@ func newRules(rules [][]string) (Rules, error) {
|
||||||
return nil, fmt.Errorf("Empty rule")
|
return nil, fmt.Errorf("Empty rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
if newRule, ok := newRuleMap[rule[0]]; ok {
|
owner := false
|
||||||
r, err = newRule(rule)
|
q := Qualifier{}
|
||||||
if err != nil {
|
qualifier:
|
||||||
return nil, err
|
switch rule.Get(0) {
|
||||||
|
// File & Link prefix
|
||||||
|
case tokOWNER:
|
||||||
|
owner = true
|
||||||
|
rule = rule[1:]
|
||||||
|
goto qualifier
|
||||||
|
// Qualifier
|
||||||
|
case tokALLOW, tokDENY:
|
||||||
|
q.AccessType = rule.Get(0)
|
||||||
|
rule = rule[1:]
|
||||||
|
goto qualifier
|
||||||
|
case tokAUDIT:
|
||||||
|
q.Audit = true
|
||||||
|
rule = rule[1:]
|
||||||
|
goto qualifier
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Line rules
|
||||||
|
if newRule, ok := newRuleMap[rule.Get(0)]; ok {
|
||||||
|
r, err = newRule(q, rule[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if owner && r.Kind() == LINK {
|
||||||
|
r.(*Link).Owner = owner
|
||||||
|
}
|
||||||
|
res = append(res, r)
|
||||||
|
} else {
|
||||||
|
raw := rule.Get(0)
|
||||||
|
if raw != "" {
|
||||||
|
// File
|
||||||
|
if isAARE(raw) || owner {
|
||||||
|
r, err = newFile(q, rule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.(*File).Owner = owner
|
||||||
|
res = append(res, r)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Unknown rule: %s", rule)
|
||||||
|
// return nil, fmt.Errorf("Unknown rule: %s", rule)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Unrecognized rule: %s", rule)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res = append(res, r)
|
|
||||||
} else if strings.HasPrefix(rule[0], VARIABLE.Tok()) {
|
|
||||||
r, err = newVariable(rule)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res = append(res, r)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Unrecognized rule: %s", rule)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AppArmorProfileFile) parsePreamble(input []string) error {
|
func (f *AppArmorProfileFile) parsePreamble(preamble string) error {
|
||||||
var err error
|
var err error
|
||||||
var r Rule
|
inHeader = true
|
||||||
var rules Rules
|
|
||||||
|
|
||||||
tokenizedRules := [][]string{}
|
// Line rules
|
||||||
for _, line := range input {
|
preamble, lineRules, err := parseLineRules(true, preamble)
|
||||||
if strings.HasPrefix(line, COMMENT.Tok()) {
|
|
||||||
r, err = newComment(strings.Split(line, " "))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rules = append(rules, r)
|
|
||||||
} else {
|
|
||||||
tokens := tokenize(line)
|
|
||||||
tokenizedRules = append(tokenizedRules, tokens)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rr, err := newRules(tokenizedRules)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.Preamble = append(f.Preamble, rules...)
|
f.Preamble = append(f.Preamble, lineRules...)
|
||||||
f.Preamble = append(f.Preamble, rr...)
|
|
||||||
return nil
|
// Comma rules
|
||||||
|
r, err := parseCommaRules(preamble)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
commaRules, err := newRules(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.Preamble = append(f.Preamble, commaRules...)
|
||||||
|
|
||||||
|
for _, r := range f.Preamble {
|
||||||
|
if r.Constraint() == blockKind {
|
||||||
|
f.Preamble = nil
|
||||||
|
return fmt.Errorf("Rule not allowed in preamble: %s", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inHeader = false
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an apparmor profile file.
|
// Parse an apparmor profile file.
|
||||||
//
|
//
|
||||||
// Only supports parsing of apparmor file preamble and profile headers.
|
|
||||||
//
|
|
||||||
// Warning: It is purposelly an uncomplete basic parser for apparmor profile,
|
// Warning: It is purposelly an uncomplete basic parser for apparmor profile,
|
||||||
// it is only aimed for internal tooling purpose. For "simplicity", it is not
|
// it is only aimed for internal tooling purpose. For "simplicity", it is not
|
||||||
// using antlr / participle. It is only used for experimental feature in the
|
// using antlr / participle. It is only used for experimental feature in the
|
||||||
// apparmor.d project.
|
// apparmor.d project.
|
||||||
//
|
//
|
||||||
// Stop at the first profile header. Does not support multiline coma rules.
|
// Very basic:
|
||||||
|
// - Only supports parsing of preamble and profile headers.
|
||||||
|
// - Stop at the first profile header.
|
||||||
|
// - Does not support multiline coma rules.
|
||||||
|
// - Does not support multiple profiles by file.
|
||||||
//
|
//
|
||||||
// Current use case:
|
// Current use case:
|
||||||
//
|
|
||||||
// - Parse include and tunables
|
// - Parse include and tunables
|
||||||
// - Parse variable in profile preamble and in tunable files
|
// - Parse variable in profile preamble and in tunable files
|
||||||
// - Parse (sub) profiles header to edit flags
|
// - Parse (sub) profiles header to edit flags
|
||||||
func (f *AppArmorProfileFile) Parse(input string) error {
|
func (f *AppArmorProfileFile) Parse(input string) (int, error) {
|
||||||
|
var raw strings.Builder
|
||||||
rawHeader := ""
|
rawHeader := ""
|
||||||
rawPreamble := []string{}
|
nb := 0
|
||||||
|
|
||||||
done:
|
done:
|
||||||
for _, line := range strings.Split(input, "\n") {
|
for i, line := range strings.Split(input, "\n") {
|
||||||
tmp := strings.TrimLeft(line, "\t ")
|
tmp := strings.TrimLeft(line, "\t ")
|
||||||
tmp = strings.TrimRight(tmp, ",")
|
|
||||||
switch {
|
switch {
|
||||||
case tmp == "":
|
case tmp == "":
|
||||||
continue
|
continue
|
||||||
case strings.HasPrefix(tmp, PROFILE.Tok()):
|
case strings.HasPrefix(tmp, PROFILE.Tok()):
|
||||||
rawHeader = tmp
|
rawHeader = strings.TrimRight(tmp, "{")
|
||||||
|
nb = i
|
||||||
break done
|
break done
|
||||||
case strings.HasPrefix(tmp, HAT.String()), strings.HasPrefix(tmp, HAT.Tok()):
|
case strings.HasPrefix(tmp, HAT.String()), strings.HasPrefix(tmp, HAT.Tok()):
|
||||||
|
nb = i
|
||||||
break done
|
break done
|
||||||
default:
|
default:
|
||||||
rawPreamble = append(rawPreamble, tmp)
|
raw.WriteString(tmp + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.parsePreamble(rawPreamble); err != nil {
|
if err := f.parsePreamble(raw.String()); err != nil {
|
||||||
return err
|
return nb, err
|
||||||
}
|
}
|
||||||
if rawHeader != "" {
|
if rawHeader != "" {
|
||||||
header, err := newHeader(tokenize(rawHeader))
|
header, err := newHeader(parseRule(rawHeader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nb, err
|
||||||
}
|
}
|
||||||
profile := &Profile{Header: header}
|
profile := &Profile{Header: header}
|
||||||
f.Profiles = append(f.Profiles, profile)
|
f.Profiles = append(f.Profiles, profile)
|
||||||
}
|
}
|
||||||
return nil
|
return nb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse apparmor profile rules by paragraphs
|
||||||
|
func ParseRules(input string) ([]Rules, []string, error) {
|
||||||
|
paragraphRules := []Rules{}
|
||||||
|
paragraphs := []string{}
|
||||||
|
|
||||||
|
for _, match := range regParagraph.FindAllStringSubmatch(input, -1) {
|
||||||
|
if len(match[0]) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore blocks header
|
||||||
|
tmp := strings.TrimLeft(match[0], "\t ")
|
||||||
|
tmp = strings.TrimRight(tmp, "\n")
|
||||||
|
var paragraph string
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(tmp, PROFILE.Tok()):
|
||||||
|
_, paragraph, _ = strings.Cut(match[0], "\n")
|
||||||
|
case strings.HasPrefix(tmp, HAT.String()), strings.HasPrefix(tmp, HAT.Tok()):
|
||||||
|
_, paragraph, _ = strings.Cut(match[0], "\n")
|
||||||
|
case strings.HasSuffix(tmp, "}"):
|
||||||
|
paragraph = strings.Replace(match[0], "}\n", "\n", 1)
|
||||||
|
default:
|
||||||
|
paragraph = match[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
paragraphs = append(paragraphs, paragraph)
|
||||||
|
rules, err := parseParagraph(paragraph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
paragraphRules = append(paragraphRules, rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paragraphRules, paragraphs, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue