diff --git a/pkg/aa/all.go b/pkg/aa/all.go index 130d25c04..2ef5441be 100644 --- a/pkg/aa/all.go +++ b/pkg/aa/all.go @@ -12,6 +12,10 @@ type All struct { RuleBase } +func newAll(q Qualifier, rule rule) (Rule, error) { + return &All{RuleBase: newBase(rule)}, nil +} + func (r *All) Validate() error { return nil } diff --git a/pkg/aa/base.go b/pkg/aa/base.go index 112f32b34..fb8174977 100644 --- a/pkg/aa/base.go +++ b/pkg/aa/base.go @@ -18,17 +18,19 @@ type RuleBase struct { Optional bool } -func newRule(rule []string) RuleBase { +func newBase(rule rule) RuleBase { comment := "" fileInherit, noNewPrivs, optional := false, false, false - idx := 0 - for idx < len(rule) { - if rule[idx] == COMMENT.Tok() { - comment = " " + strings.Join(rule[idx+1:], " ") - break + if len(rule) > 0 { + if len(rule.Get(0)) > 0 && rule.Get(0)[0] == '#' { + // Line rule is a comment + rule = rule[1:] + comment = rule.GetString() + } else { + // Comma rule, with comment at the end + comment = rule[len(rule)-1].comment } - idx++ } switch { case strings.Contains(comment, "file_inherit"): diff --git a/pkg/aa/capability.go b/pkg/aa/capability.go index 7f370c575..f9f083f98 100644 --- a/pkg/aa/capability.go +++ b/pkg/aa/capability.go @@ -31,6 +31,18 @@ type Capability struct { Names []string } +func newCapability(q Qualifier, rule rule) (Rule, error) { + names, err := toValues(CAPABILITY, "name", rule.GetString()) + if err != nil { + return nil, err + } + return &Capability{ + RuleBase: newBase(rule), + Qualifier: q, + Names: names, + }, nil +} + func newCapabilityFromLog(log map[string]string) Rule { return &Capability{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/change_profile.go b/pkg/aa/change_profile.go index 061389727..a6abb8772 100644 --- a/pkg/aa/change_profile.go +++ b/pkg/aa/change_profile.go @@ -4,7 +4,10 @@ package aa -import "fmt" +import ( + "fmt" + "slices" +) const CHANGEPROFILE Kind = "change_profile" @@ -22,6 +25,38 @@ type ChangeProfile struct { ProfileName string } +func newChangeProfile(q Qualifier, rule rule) (Rule, error) { + mode, exec, target := "", "", "" + if len(rule) > 0 { + if slices.Contains(requirements[CHANGEPROFILE]["mode"], rule.Get(0)) { + mode = rule.Get(0) + rule = rule[1:] + } + if len(rule) > 0 { + if rule.Get(0) != tokARROW { + exec = rule.Get(0) + if len(rule) > 2 { + if rule.Get(1) != tokARROW { + return nil, fmt.Errorf("missing '%s' in rule: %s", tokARROW, rule) + } + target = rule.Get(2) + } + } else { + if len(rule) > 1 { + target = rule.Get(1) + } + } + } + } + return &ChangeProfile{ + RuleBase: newBase(rule), + Qualifier: q, + ExecMode: mode, + Exec: exec, + ProfileName: target, + }, nil +} + func newChangeProfileFromLog(log map[string]string) Rule { return &ChangeProfile{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/dbus.go b/pkg/aa/dbus.go index f1f880a14..a3f679df2 100644 --- a/pkg/aa/dbus.go +++ b/pkg/aa/dbus.go @@ -33,6 +33,25 @@ type Dbus struct { PeerLabel string } +func newDbus(q Qualifier, rule rule) (Rule, error) { + accesses, err := toAccess(DBUS, rule.GetString()) + if err != nil { + return nil, err + } + return &Dbus{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Bus: rule.GetValuesAsString("bus"), + Name: rule.GetValuesAsString("name"), + Path: rule.GetValuesAsString("path"), + Interface: rule.GetValuesAsString("interface"), + Member: rule.GetValuesAsString("member"), + PeerName: rule.GetValues("peer").GetValuesAsString("name"), + PeerLabel: rule.GetValues("peer").GetValuesAsString("label"), + }, nil +} + func newDbusFromLog(log map[string]string) Rule { name := "" peerName := "" diff --git a/pkg/aa/file.go b/pkg/aa/file.go index a74025742..3e27bfbb8 100644 --- a/pkg/aa/file.go +++ b/pkg/aa/file.go @@ -46,6 +46,45 @@ type File struct { Target string } +func newFile(q Qualifier, rule rule) (Rule, error) { + path, access, target, owner := "", "", "", false + if len(rule) > 0 { + if rule.Get(0) == tokOWNER { + owner = true + rule = rule[1:] + } + if rule.Get(0) == FILE.Tok() { + rule = rule[1:] + } + + r := rule.GetSlice() + size := len(r) + if size < 2 { + return nil, fmt.Errorf("missing file or access in rule: %s", rule) + } + + path, access = r[0], r[1] + if size > 2 { + if r[2] != tokARROW { + return nil, fmt.Errorf("missing '%s' in rule: %s", tokARROW, rule) + } + target = r[3] + } + } + accesses, err := toAccess(FILE, access) + if err != nil { + return nil, err + } + return &File{ + RuleBase: newBase(rule), + Qualifier: q, + Owner: owner, + Path: path, + Access: accesses, + Target: target, + }, nil +} + func newFileFromLog(log map[string]string) Rule { accesses, err := toAccess("file-log", log["requested_mask"]) if err != nil { @@ -112,6 +151,40 @@ type Link struct { Target string } +func newLink(q Qualifier, rule rule) (Rule, error) { + owner, subset, path, target := false, false, "", "" + if len(rule) > 0 { + if rule.Get(0) == tokOWNER { + owner = true + rule = rule[1:] + } + if len(rule) > 0 && rule.Get(0) == tokSUBSET { + subset = true + rule = rule[1:] + } + + r := rule.GetSlice() + size := len(r) + if size > 0 { + path = r[0] + } + if size > 2 { + if r[1] != tokARROW { + return nil, fmt.Errorf("missing '%s' in rule: %s", tokARROW, rule) + } + target = r[2] + } + } + return &Link{ + RuleBase: newBase(rule), + Qualifier: q, + Owner: owner, + Subset: subset, + Path: path, + Target: target, + }, nil +} + func newLinkFromLog(log map[string]string) Rule { return &Link{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/io_uring.go b/pkg/aa/io_uring.go index ad18a6104..f7adff8f2 100644 --- a/pkg/aa/io_uring.go +++ b/pkg/aa/io_uring.go @@ -23,6 +23,19 @@ type IOUring struct { Label string } +func newIOUring(q Qualifier, rule rule) (Rule, error) { + accesses, err := toAccess(IOURING, rule.GetString()) + if err != nil { + return nil, err + } + return &IOUring{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Label: rule.GetValuesAsString("label"), + }, nil +} + func newIOUringFromLog(log map[string]string) Rule { return &IOUring{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/mount.go b/pkg/aa/mount.go index f2efc143a..4d7928b00 100644 --- a/pkg/aa/mount.go +++ b/pkg/aa/mount.go @@ -33,6 +33,17 @@ type MountConditions struct { Options []string } +func newMountConditions(rule rule) (MountConditions, error) { + options, err := toValues(MOUNT, "flags", rule.GetValuesAsString("options")) + if err != nil { + return MountConditions{}, err + } + return MountConditions{ + FsType: rule.GetValuesAsString("fstype"), + Options: options, + }, nil +} + func newMountConditionsFromLog(log map[string]string) MountConditions { if _, present := log["flags"]; present { return MountConditions{ @@ -62,6 +73,35 @@ type Mount struct { MountPoint string } +func newMount(q Qualifier, rule rule) (Rule, error) { + mount, src := "", "" + r := rule.GetSlice() + if len(r) > 0 { + if r[0] != tokARROW { + src = r[0] + r = r[1:] + } + if len(r) == 2 { + if r[0] != tokARROW { + return nil, fmt.Errorf("missing '%s' in rule: %s", tokARROW, rule) + } + mount = r[1] + } + } + + conditions, err := newMountConditions(rule) + if err != nil { + return nil, err + } + return &Mount{ + RuleBase: newBase(rule), + Qualifier: q, + MountConditions: conditions, + Source: src, + MountPoint: mount, + }, nil +} + func newMountFromLog(log map[string]string) Rule { return &Mount{ RuleBase: newBaseFromLog(log), @@ -112,6 +152,24 @@ type Umount struct { MountPoint string } +func newUmount(q Qualifier, rule rule) (Rule, error) { + mount := "" + r := rule.GetSlice() + if len(r) > 0 { + mount = r[0] + } + conditions, err := newMountConditions(rule) + if err != nil { + return nil, err + } + return &Umount{ + RuleBase: newBase(rule), + Qualifier: q, + MountConditions: conditions, + MountPoint: mount, + }, nil +} + func newUmountFromLog(log map[string]string) Rule { return &Umount{ RuleBase: newBaseFromLog(log), @@ -158,6 +216,25 @@ type Remount struct { MountPoint string } +func newRemount(q Qualifier, rule rule) (Rule, error) { + mount := "" + r := rule.GetSlice() + if len(r) > 0 { + mount = r[0] + } + + conditions, err := newMountConditions(rule) + if err != nil { + return nil, err + } + return &Remount{ + RuleBase: newBase(rule), + Qualifier: q, + MountConditions: conditions, + MountPoint: mount, + }, nil +} + func newRemountFromLog(log map[string]string) Rule { return &Remount{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/mqueue.go b/pkg/aa/mqueue.go index 2428b9042..e8d4dffd6 100644 --- a/pkg/aa/mqueue.go +++ b/pkg/aa/mqueue.go @@ -31,6 +31,31 @@ type Mqueue struct { Name string } +func newMqueue(q Qualifier, rule rule) (Rule, error) { + access, name := "", "" + r := rule.GetSlice() + size := len(r) + if size > 0 { + access = strings.Join(r[:size-1], " ") + name = r[size-1] + if slices.Contains(requirements[MQUEUE]["access"], name) { + access += " " + name + } + } + accesses, err := toAccess(MQUEUE, access) + if err != nil { + return nil, err + } + return &Mqueue{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Type: rule.GetValuesAsString("type"), + Label: rule.GetValuesAsString("label"), + Name: name, + }, nil +} + func newMqueueFromLog(log map[string]string) Rule { mqueueType := "posix" if strings.Contains(log["class"], "posix") { diff --git a/pkg/aa/network.go b/pkg/aa/network.go index 4051ee1f5..0478b310c 100644 --- a/pkg/aa/network.go +++ b/pkg/aa/network.go @@ -6,6 +6,7 @@ package aa import ( "fmt" + "slices" ) const NETWORK Kind = "network" @@ -70,6 +71,28 @@ type Network struct { Protocol string } +func newNetwork(q Qualifier, rule rule) (Rule, error) { + nType, protocol, domain := "", "", "" + r := rule.GetSlice() + if len(r) > 0 { + domain = r[0] + } + if len(r) >= 2 { + if slices.Contains(requirements[NETWORK]["type"], r[1]) { + nType = r[1] + } else if slices.Contains(requirements[NETWORK]["protocol"], r[1]) { + protocol = r[1] + } + } + return &Network{ + RuleBase: newBase(rule), + Qualifier: q, + Domain: domain, + Type: nType, + Protocol: protocol, + }, nil +} + func newNetworkFromLog(log map[string]string) Rule { return &Network{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/parse.go b/pkg/aa/parse.go index 00c280859..0dd0d37fe 100644 --- a/pkg/aa/parse.go +++ b/pkg/aa/parse.go @@ -30,6 +30,26 @@ const ( var ( newRuleMap = map[string]func(q Qualifier, r rule) (Rule, error){ + ABI.Tok(): newAbi, + ALIAS.Tok(): newAlias, + ALL.Tok(): newAll, + "set": newRlimit, + USERNS.Tok(): newUserns, + CAPABILITY.Tok(): newCapability, + NETWORK.Tok(): newNetwork, + MOUNT.Tok(): newMount, + UMOUNT.Tok(): newUmount, + REMOUNT.Tok(): newRemount, + MQUEUE.Tok(): newMqueue, + IOURING.Tok(): newIOUring, + PIVOTROOT.Tok(): newPivotRoot, + CHANGEPROFILE.Tok(): newChangeProfile, + SIGNAL.Tok(): newSignal, + PTRACE.Tok(): newPtrace, + UNIX.Tok(): newUnix, + DBUS.Tok(): newDbus, + FILE.Tok(): newFile, + LINK.Tok(): newLink, } tok = map[Kind]string{ diff --git a/pkg/aa/pivot_root.go b/pkg/aa/pivot_root.go index 7ac0b02f6..cfa6833ad 100644 --- a/pkg/aa/pivot_root.go +++ b/pkg/aa/pivot_root.go @@ -4,6 +4,8 @@ package aa +import "fmt" + const PIVOTROOT Kind = "pivot_root" type PivotRoot struct { @@ -14,6 +16,30 @@ type PivotRoot struct { TargetProfile string } +func newPivotRoot(q Qualifier, rule rule) (Rule, error) { + newroot, target := "", "" + r := rule.GetSlice() + if len(r) > 0 { + if r[0] != tokARROW { + newroot = r[0] + r = r[1:] + } + if len(r) == 2 { + if r[0] != tokARROW { + return nil, fmt.Errorf("missing '%s' in rule: %s", tokARROW, rule) + } + target = r[1] + } + } + return &PivotRoot{ + RuleBase: newBase(rule), + Qualifier: q, + OldRoot: rule.GetValuesAsString("oldroot"), + NewRoot: newroot, + TargetProfile: target, + }, nil +} + func newPivotRootFromLog(log map[string]string) Rule { return &PivotRoot{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/preamble.go b/pkg/aa/preamble.go index 552b16481..71e54458f 100644 --- a/pkg/aa/preamble.go +++ b/pkg/aa/preamble.go @@ -23,8 +23,8 @@ type Comment struct { RuleBase } -func newComment(rule []string) (Rule, error) { - base := newRule(rule) +func newComment(rule rule) (Rule, error) { + base := newBase(rule) base.IsLineRule = true return &Comment{RuleBase: base}, nil } @@ -41,10 +41,6 @@ func (r *Comment) String() string { return renderTemplate(r.Kind(), r) } -func (r *Comment) IsPreamble() bool { - return true -} - func (r *Comment) Constraint() constraint { return anyKind } @@ -59,16 +55,13 @@ type Abi struct { IsMagic bool } -func newAbi(rule []string) (Rule, error) { +func newAbi(q Qualifier, rule rule) (Rule, error) { var magic bool - if len(rule) > 0 && rule[0] == ABI.Tok() { - rule = rule[1:] - } if len(rule) != 1 { return nil, fmt.Errorf("invalid abi format: %s", rule) } - path := rule[0] + path := rule.Get(0) switch { case path[0] == '"': magic = false @@ -78,7 +71,7 @@ func newAbi(rule []string) (Rule, error) { return nil, fmt.Errorf("invalid path %s in rule: %s", path, rule) } return &Abi{ - RuleBase: newRule(rule), + RuleBase: newBase(rule), Path: strings.Trim(path, "\"<>"), IsMagic: magic, }, nil @@ -114,20 +107,17 @@ type Alias struct { RewrittenPath string } -func newAlias(rule []string) (Rule, error) { - if len(rule) > 0 && rule[0] == ALIAS.Tok() { - rule = rule[1:] - } +func newAlias(q Qualifier, rule rule) (Rule, error) { if len(rule) != 3 { return nil, fmt.Errorf("invalid alias format: %s", rule) } - if rule[1] != tokARROW { + if rule.Get(1) != tokARROW { return nil, fmt.Errorf("invalid alias format, missing %s in: %s", tokARROW, rule) } return &Alias{ - RuleBase: newRule(rule), - Path: rule[0], - RewrittenPath: rule[2], + RuleBase: newBase(rule), + Path: rule.Get(0), + RewrittenPath: rule.Get(2), }, nil } @@ -162,25 +152,22 @@ type Include struct { IsMagic bool } -func newInclude(rule []string) (Rule, error) { +func newInclude(rule rule) (Rule, error) { var magic bool var ifexists bool - if len(rule) > 0 && rule[0] == INCLUDE.Tok() { - rule = rule[1:] - } - size := len(rule) if size == 0 { return nil, fmt.Errorf("invalid include format: %v", rule) } - if size >= 3 && strings.Join(rule[:2], " ") == tokIFEXISTS { + r := rule.GetSlice() + if size >= 3 && strings.Join(r[:2], " ") == tokIFEXISTS { ifexists = true - rule = rule[2:] + r = r[2:] } - path := rule[0] + path := r[0] switch { case path[0] == '"': magic = false @@ -190,7 +177,7 @@ func newInclude(rule []string) (Rule, error) { return nil, fmt.Errorf("invalid path format: %v", path) } return &Include{ - RuleBase: newRule(rule), + RuleBase: newBase(rule), IfExists: ifexists, Path: strings.Trim(path, "\"<>"), IsMagic: magic, @@ -238,29 +225,27 @@ type Variable struct { Define bool } -func newVariable(rule []string) (Rule, error) { +func newVariableFromRule(rule rule) (Rule, error) { var define bool var values []string if len(rule) < 3 { return nil, fmt.Errorf("invalid variable format: %v", rule) } - name := strings.Trim(rule[0], VARIABLE.Tok()+"}") - switch rule[1] { + r := rule.GetSlice() + name := strings.Trim(rule.Get(0), VARIABLE.Tok()+"}") + switch rule.Get(1) { case tokEQUAL: define = true - values = tokensStripComment(rule[2:]) - case tokPLUS: - if rule[2] != tokEQUAL { - return nil, fmt.Errorf("invalid operator in variable: %v", rule) - } + values = r[2:] + case tokPLUS + tokEQUAL: define = false - values = tokensStripComment(rule[3:]) + values = r[2:] default: return nil, fmt.Errorf("invalid operator in variable: %v", rule) } return &Variable{ - RuleBase: newRule(rule), + RuleBase: newBase(rule), Name: name, Values: values, Define: define, diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index 138ce6578..ee2b7a3b7 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -43,48 +43,29 @@ type Header struct { Flags []string } -func newHeader(rule []string) (Header, error) { +func newHeader(rule rule) (Header, error) { if len(rule) == 0 { return Header{}, nil } - if rule[len(rule)-1] == "{" { - rule = rule[:len(rule)-1] - } - if rule[0] == PROFILE.Tok() { + if rule.Get(0) == PROFILE.Tok() { rule = rule[1:] } - - delete := []int{} - flags := []string{} - attributes := make(map[string]string) - for idx, token := range rule { - if item, ok := strings.CutPrefix(token, tokFLAGS+"="); ok { - flags = tokenToSlice(item) - delete = append(delete, idx) - } else if item, ok := strings.CutPrefix(token, tokATTRIBUTES+"="); ok { - for _, m := range tokenToSlice(item) { - kv := strings.SplitN(m, "=", 2) - attributes[kv[0]] = kv[1] - } - delete = append(delete, idx) - } - } - for i := len(delete) - 1; i >= 0; i-- { - rule = slices.Delete(rule, delete[i], delete[i]+1) - } - name, attachments := "", []string{} if len(rule) >= 1 { - name = rule[0] + name = rule.Get(0) if len(rule) > 1 { - attachments = rule[1:] + attachments = rule[1:].GetSlice() } } + attributes := make(map[string]string) + for k, v := range rule.GetValues(tokATTRIBUTES).GetAsMap() { + attributes[k] = strings.Join(v, "") + } return Header{ Name: name, Attachments: attachments, Attributes: attributes, - Flags: flags, + Flags: rule.GetValuesAsSlice(tokFLAGS), }, nil } diff --git a/pkg/aa/ptrace.go b/pkg/aa/ptrace.go index 7614c69c7..2a5109a6d 100644 --- a/pkg/aa/ptrace.go +++ b/pkg/aa/ptrace.go @@ -25,6 +25,19 @@ type Ptrace struct { Peer string } +func newPtrace(q Qualifier, rule rule) (Rule, error) { + accesses, err := toAccess(PTRACE, rule.GetString()) + if err != nil { + return nil, err + } + return &Ptrace{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Peer: rule.GetValuesAsString("peer"), + }, nil +} + func newPtraceFromLog(log map[string]string) Rule { return &Ptrace{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/rlimit.go b/pkg/aa/rlimit.go index 5491446e7..959ac4eec 100644 --- a/pkg/aa/rlimit.go +++ b/pkg/aa/rlimit.go @@ -27,6 +27,21 @@ type Rlimit struct { Value string } +func newRlimit(q Qualifier, rule rule) (Rule, error) { + if len(rule) != 4 { + return nil, fmt.Errorf("invalid set format: %s", rule) + } + if rule.Get(0) != RLIMIT.Tok() { + return nil, fmt.Errorf("invalid rlimit format: %s", rule) + } + return &Rlimit{ + RuleBase: newBase(rule), + Key: rule.Get(1), + Op: rule.Get(2), + Value: rule.Get(3), + }, nil +} + func newRlimitFromLog(log map[string]string) Rule { return &Rlimit{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/signal.go b/pkg/aa/signal.go index 514fd6b99..8e674df29 100644 --- a/pkg/aa/signal.go +++ b/pkg/aa/signal.go @@ -39,6 +39,24 @@ type Signal struct { Peer string } +func newSignal(q Qualifier, rule rule) (Rule, error) { + accesses, err := toAccess(SIGNAL, rule.GetString()) + if err != nil { + return nil, err + } + set, err := toValues(SIGNAL, "set", rule.GetValuesAsString("set")) + if err != nil { + return nil, err + } + return &Signal{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Set: set, + Peer: rule.GetValuesAsString("peer"), + }, nil +} + func newSignalFromLog(log map[string]string) Rule { return &Signal{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/unix.go b/pkg/aa/unix.go index 23066fdaf..f65c953d3 100644 --- a/pkg/aa/unix.go +++ b/pkg/aa/unix.go @@ -34,6 +34,26 @@ type Unix struct { PeerAddr string } +func newUnix(q Qualifier, rule rule) (Rule, error) { + accesses, err := toAccess(UNIX, rule.GetString()) + if err != nil { + return nil, err + } + return &Unix{ + RuleBase: newBase(rule), + Qualifier: q, + Access: accesses, + Type: rule.GetValuesAsString("type"), + Protocol: rule.GetValuesAsString("protocol"), + Address: rule.GetValuesAsString("addr"), + Label: rule.GetValuesAsString("label"), + Attr: rule.GetValuesAsString("attr"), + Opt: rule.GetValuesAsString("opt"), + PeerLabel: rule.GetValues("peer").GetValuesAsString("label"), + PeerAddr: rule.GetValues("peer").GetValuesAsString("addr"), + }, nil +} + func newUnixFromLog(log map[string]string) Rule { return &Unix{ RuleBase: newBaseFromLog(log), diff --git a/pkg/aa/userns.go b/pkg/aa/userns.go index 28bc948c6..a28870c94 100644 --- a/pkg/aa/userns.go +++ b/pkg/aa/userns.go @@ -14,6 +14,26 @@ type Userns struct { Create bool } +func newUserns(q Qualifier, rule rule) (Rule, error) { + var create bool + switch len(rule) { + case 0: + create = true + case 1: + if rule.Get(0) != "create" { + return nil, fmt.Errorf("invalid userns format: %s", rule) + } + create = true + default: + return nil, fmt.Errorf("invalid userns format: %s", rule) + } + return &Userns{ + RuleBase: newBase(rule), + Qualifier: q, + Create: create, + }, nil +} + func newUsernsFromLog(log map[string]string) Rule { return &Userns{ RuleBase: newBaseFromLog(log),