mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2024-11-14 23:43:56 +01:00
feat(aa): add the Kind struct to manage aa rules.
This commit is contained in:
parent
1333ec2025
commit
3b0944c615
@ -24,7 +24,7 @@ func newRule(rule []string) RuleBase {
|
||||
|
||||
idx := 0
|
||||
for idx < len(rule) {
|
||||
if rule[idx] == tokCOMMENT {
|
||||
if rule[idx] == COMMENT.Tok() {
|
||||
comment = " " + strings.Join(rule[idx+1:], " ")
|
||||
break
|
||||
}
|
||||
@ -85,15 +85,15 @@ func (r RuleBase) Equals(other any) bool {
|
||||
}
|
||||
|
||||
func (r RuleBase) String() string {
|
||||
return renderTemplate("comment", r)
|
||||
return renderTemplate(r.Kind(), r)
|
||||
}
|
||||
|
||||
func (r RuleBase) Constraint() constraint {
|
||||
return anyKind
|
||||
}
|
||||
|
||||
func (r RuleBase) Kind() string {
|
||||
return "base"
|
||||
func (r RuleBase) Kind() Kind {
|
||||
return COMMENT
|
||||
}
|
||||
|
||||
type Qualifier struct {
|
||||
|
@ -5,8 +5,7 @@
|
||||
package aa
|
||||
|
||||
const (
|
||||
tokHAT = "hat"
|
||||
tokCARET = "^"
|
||||
HAT Kind = "hat"
|
||||
)
|
||||
|
||||
// Hat represents a single AppArmor hat.
|
||||
@ -26,7 +25,7 @@ func (p *Hat) Less(other any) bool {
|
||||
}
|
||||
|
||||
func (p *Hat) Equals(other any) bool {
|
||||
o, _ := other.(*Profile)
|
||||
o, _ := other.(*Hat)
|
||||
return p.Name == o.Name
|
||||
}
|
||||
|
||||
@ -38,6 +37,6 @@ func (p *Hat) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (p *Hat) Kind() string {
|
||||
return tokHAT
|
||||
func (p *Hat) Kind() Kind {
|
||||
return HAT
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokCAPABILITY = "capability"
|
||||
const CAPABILITY Kind = "capability"
|
||||
|
||||
func init() {
|
||||
requirements[tokCAPABILITY] = requirement{
|
||||
requirements[CAPABILITY] = requirement{
|
||||
"name": {
|
||||
"audit_control", "audit_read", "audit_write", "block_suspend", "bpf",
|
||||
"checkpoint_restore", "chown", "dac_override", "dac_read_search",
|
||||
@ -36,7 +36,7 @@ func newCapabilityFromLog(log map[string]string) Rule {
|
||||
return &Capability{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Names: Must(toValues(tokCAPABILITY, "name", log["capname"])),
|
||||
Names: Must(toValues(CAPABILITY, "name", log["capname"])),
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,6 @@ func (r *Capability) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Capability) Kind() string {
|
||||
return tokCAPABILITY
|
||||
func (r *Capability) Kind() Kind {
|
||||
return CAPABILITY
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ package aa
|
||||
|
||||
import "fmt"
|
||||
|
||||
const tokCHANGEPROFILE = "change_profile"
|
||||
const CHANGEPROFILE Kind = "change_profile"
|
||||
|
||||
func init() {
|
||||
requirements[tokCHANGEPROFILE] = requirement{
|
||||
requirements[CHANGEPROFILE] = requirement{
|
||||
"mode": []string{"safe", "unsafe"},
|
||||
}
|
||||
}
|
||||
@ -67,6 +67,6 @@ func (r *ChangeProfile) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *ChangeProfile) Kind() string {
|
||||
return tokCHANGEPROFILE
|
||||
func (r *ChangeProfile) Kind() Kind {
|
||||
return CHANGEPROFILE
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokDBUS = "dbus"
|
||||
const DBUS Kind = "dbus"
|
||||
|
||||
func init() {
|
||||
requirements[tokDBUS] = requirement{
|
||||
requirements[DBUS] = requirement{
|
||||
"access": []string{
|
||||
"send", "receive", "bind", "eavesdrop", "r", "read",
|
||||
"w", "write", "rw",
|
||||
@ -110,6 +110,6 @@ func (r *Dbus) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Dbus) Kind() string {
|
||||
return tokDBUS
|
||||
func (r *Dbus) Kind() Kind {
|
||||
return DBUS
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tokLINK = "link"
|
||||
tokFILE = "file"
|
||||
tokOWNER = "owner"
|
||||
tokSUBSET = "subset"
|
||||
LINK Kind = "link"
|
||||
FILE Kind = "file"
|
||||
tokOWNER = "owner"
|
||||
tokSUBSET = "subset"
|
||||
)
|
||||
|
||||
func init() {
|
||||
requirements[tokFILE] = requirement{
|
||||
requirements[FILE] = requirement{
|
||||
"access": {"m", "r", "w", "l", "k"},
|
||||
"transition": {
|
||||
"ix", "ux", "Ux", "px", "Px", "cx", "Cx", "pix", "Pix", "cix",
|
||||
@ -40,15 +40,15 @@ func isOwner(log map[string]string) bool {
|
||||
// cmpFileAccess compares two access strings for file rules.
|
||||
// It is aimed to be used in slices.SortFunc.
|
||||
func cmpFileAccess(i, j string) int {
|
||||
if slices.Contains(requirements[tokFILE]["access"], i) &&
|
||||
slices.Contains(requirements[tokFILE]["access"], j) {
|
||||
return requirementsWeights[tokFILE]["access"][i] - requirementsWeights[tokFILE]["access"][j]
|
||||
if slices.Contains(requirements[FILE]["access"], i) &&
|
||||
slices.Contains(requirements[FILE]["access"], j) {
|
||||
return requirementsWeights[FILE]["access"][i] - requirementsWeights[FILE]["access"][j]
|
||||
}
|
||||
if slices.Contains(requirements[tokFILE]["transition"], i) &&
|
||||
slices.Contains(requirements[tokFILE]["transition"], j) {
|
||||
return requirementsWeights[tokFILE]["transition"][i] - requirementsWeights[tokFILE]["transition"][j]
|
||||
if slices.Contains(requirements[FILE]["transition"], i) &&
|
||||
slices.Contains(requirements[FILE]["transition"], j) {
|
||||
return requirementsWeights[FILE]["transition"][i] - requirementsWeights[FILE]["transition"][j]
|
||||
}
|
||||
if slices.Contains(requirements[tokFILE]["access"], i) {
|
||||
if slices.Contains(requirements[FILE]["access"], i) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
@ -121,8 +121,8 @@ func (r *File) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *File) Kind() string {
|
||||
return tokFILE
|
||||
func (r *File) Kind() Kind {
|
||||
return FILE
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
@ -179,6 +179,6 @@ func (r *Link) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Link) Kind() string {
|
||||
return tokLINK
|
||||
func (r *Link) Kind() Kind {
|
||||
return LINK
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokIOURING = "io_uring"
|
||||
const IOURING Kind = "io_uring"
|
||||
|
||||
func init() {
|
||||
requirements[tokIOURING] = requirement{
|
||||
requirements[IOURING] = requirement{
|
||||
"access": []string{"sqpoll", "override_creds"},
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ func newIOUringFromLog(log map[string]string) Rule {
|
||||
return &IOUring{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Access: Must(toAccess(tokIOURING, log["requested"])),
|
||||
Access: Must(toAccess(IOURING, log["requested"])),
|
||||
Label: log["label"],
|
||||
}
|
||||
}
|
||||
@ -64,6 +64,6 @@ func (r *IOUring) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *IOUring) Kind() string {
|
||||
return tokIOURING
|
||||
func (r *IOUring) Kind() Kind {
|
||||
return IOURING
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tokMOUNT = "mount"
|
||||
tokREMOUNT = "remount"
|
||||
tokUMOUNT = "umount"
|
||||
MOUNT Kind = "mount"
|
||||
REMOUNT Kind = "remount"
|
||||
UMOUNT Kind = "umount"
|
||||
)
|
||||
|
||||
func init() {
|
||||
requirements[tokMOUNT] = requirement{
|
||||
requirements[MOUNT] = requirement{
|
||||
"flags": {
|
||||
"acl", "async", "atime", "ro", "rw", "bind", "rbind", "dev",
|
||||
"diratime", "dirsync", "exec", "iversion", "loud", "mand", "move",
|
||||
@ -38,14 +38,14 @@ func newMountConditionsFromLog(log map[string]string) MountConditions {
|
||||
if _, present := log["flags"]; present {
|
||||
return MountConditions{
|
||||
FsType: log["fstype"],
|
||||
Options: Must(toValues(tokMOUNT, "flags", log["flags"])),
|
||||
Options: Must(toValues(MOUNT, "flags", log["flags"])),
|
||||
}
|
||||
}
|
||||
return MountConditions{FsType: log["fstype"]}
|
||||
}
|
||||
|
||||
func (m MountConditions) Validate() error {
|
||||
return validateValues(tokMOUNT, "flags", m.Options)
|
||||
return validateValues(MOUNT, "flags", m.Options)
|
||||
}
|
||||
|
||||
func (m MountConditions) Less(other MountConditions) bool {
|
||||
@ -113,8 +113,8 @@ func (r *Mount) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Mount) Kind() string {
|
||||
return tokMOUNT
|
||||
func (r *Mount) Kind() Kind {
|
||||
return MOUNT
|
||||
}
|
||||
|
||||
type Umount struct {
|
||||
@ -166,8 +166,8 @@ func (r *Umount) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Umount) Kind() string {
|
||||
return tokUMOUNT
|
||||
func (r *Umount) Kind() Kind {
|
||||
return UMOUNT
|
||||
}
|
||||
|
||||
type Remount struct {
|
||||
@ -219,6 +219,6 @@ func (r *Remount) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Remount) Kind() string {
|
||||
return tokREMOUNT
|
||||
func (r *Remount) Kind() Kind {
|
||||
return REMOUNT
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const tokMQUEUE = "mqueue"
|
||||
const MQUEUE Kind = "mqueue"
|
||||
|
||||
func init() {
|
||||
requirements[tokMQUEUE] = requirement{
|
||||
requirements[MQUEUE] = requirement{
|
||||
"access": []string{
|
||||
"r", "w", "rw", "read", "write", "create", "open",
|
||||
"delete", "getattr", "setattr",
|
||||
@ -41,7 +41,7 @@ func newMqueueFromLog(log map[string]string) Rule {
|
||||
return &Mqueue{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Access: Must(toAccess(tokMQUEUE, log["requested"])),
|
||||
Access: Must(toAccess(MQUEUE, log["requested"])),
|
||||
Type: mqueueType,
|
||||
Label: log["label"],
|
||||
Name: log["name"],
|
||||
@ -86,6 +86,6 @@ func (r *Mqueue) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Mqueue) Kind() string {
|
||||
return tokMQUEUE
|
||||
func (r *Mqueue) Kind() Kind {
|
||||
return MQUEUE
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const tokNETWORK = "network"
|
||||
const NETWORK Kind = "network"
|
||||
|
||||
func init() {
|
||||
requirements[tokNETWORK] = requirement{
|
||||
requirements[NETWORK] = requirement{
|
||||
"access": []string{
|
||||
"create", "bind", "listen", "accept", "connect", "shutdown",
|
||||
"getattr", "setattr", "getopt", "setopt", "send", "receive",
|
||||
@ -126,6 +126,6 @@ func (r *Network) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Network) Kind() string {
|
||||
return tokNETWORK
|
||||
func (r *Network) Kind() Kind {
|
||||
return NETWORK
|
||||
}
|
||||
|
@ -26,12 +26,16 @@ const (
|
||||
|
||||
var (
|
||||
newRuleMap = map[string]func([]string) (Rule, error){
|
||||
tokCOMMENT: newComment,
|
||||
tokABI: newAbi,
|
||||
tokALIAS: newAlias,
|
||||
tokINCLUDE: newInclude,
|
||||
COMMENT.Tok(): newComment,
|
||||
ABI.Tok(): newAbi,
|
||||
ALIAS.Tok(): newAlias,
|
||||
INCLUDE.Tok(): newInclude,
|
||||
}
|
||||
|
||||
tok = map[Kind]string{
|
||||
COMMENT: "#",
|
||||
VARIABLE: "@{",
|
||||
}
|
||||
openBlocks = []rune{tokOPENPAREN, tokOPENBRACE, tokOPENBRACKET}
|
||||
closeBlocks = []rune{tokCLOSEPAREN, tokCLOSEBRACE, tokCLOSEBRACKET}
|
||||
)
|
||||
@ -53,7 +57,7 @@ func tokenize(str string) []string {
|
||||
|
||||
blockStack := []rune{}
|
||||
tokens := make([]string, 0, len(str)/2)
|
||||
if len(str) > 2 && str[0:2] == tokVARIABLE {
|
||||
if len(str) > 2 && str[0:2] == VARIABLE.Tok() {
|
||||
isVariable = true
|
||||
}
|
||||
for _, r := range str {
|
||||
@ -122,7 +126,7 @@ func tokenToSlice(token string) []string {
|
||||
func tokensStripComment(tokens []string) []string {
|
||||
res := []string{}
|
||||
for _, v := range tokens {
|
||||
if v == tokCOMMENT {
|
||||
if v == COMMENT.Tok() {
|
||||
break
|
||||
}
|
||||
res = append(res, v)
|
||||
@ -147,7 +151,7 @@ func newRules(rules [][]string) (Rules, error) {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, r)
|
||||
} else if strings.HasPrefix(rule[0], tokVARIABLE) {
|
||||
} else if strings.HasPrefix(rule[0], VARIABLE.Tok()) {
|
||||
r, err = newVariable(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -167,7 +171,7 @@ func (f *AppArmorProfileFile) parsePreamble(input []string) error {
|
||||
|
||||
tokenizedRules := [][]string{}
|
||||
for _, line := range input {
|
||||
if strings.HasPrefix(line, tokCOMMENT) {
|
||||
if strings.HasPrefix(line, COMMENT.Tok()) {
|
||||
r, err = newComment(strings.Split(line, " "))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -215,7 +219,7 @@ done:
|
||||
switch {
|
||||
case tmp == "":
|
||||
continue
|
||||
case strings.HasPrefix(tmp, tokPROFILE):
|
||||
case strings.HasPrefix(tmp, PROFILE.Tok()):
|
||||
rawHeader = tmp
|
||||
break done
|
||||
default:
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
package aa
|
||||
|
||||
const tokPIVOTROOT = "pivot_root"
|
||||
const PIVOTROOT = "pivot_root"
|
||||
|
||||
type PivotRoot struct {
|
||||
RuleBase
|
||||
@ -57,6 +57,6 @@ func (r *PivotRoot) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *PivotRoot) Kind() string {
|
||||
return tokPIVOTROOT
|
||||
func (r *PivotRoot) Kind() Kind {
|
||||
return PIVOTROOT
|
||||
}
|
||||
|
@ -11,12 +11,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tokABI = "abi"
|
||||
tokALIAS = "alias"
|
||||
tokINCLUDE = "include"
|
||||
ABI Kind = "abi"
|
||||
ALIAS Kind = "alias"
|
||||
INCLUDE Kind = "include"
|
||||
VARIABLE Kind = "variable"
|
||||
COMMENT Kind = "comment"
|
||||
|
||||
tokIFEXISTS = "if exists"
|
||||
tokVARIABLE = "@{"
|
||||
tokCOMMENT = "#"
|
||||
)
|
||||
|
||||
type Comment struct {
|
||||
@ -42,7 +43,7 @@ func (r *Comment) Equals(other any) bool {
|
||||
}
|
||||
|
||||
func (r *Comment) String() string {
|
||||
return renderTemplate("comment", r)
|
||||
return renderTemplate(r.Kind(), r)
|
||||
}
|
||||
|
||||
func (r *Comment) IsPreamble() bool {
|
||||
@ -53,8 +54,8 @@ func (r *Comment) Constraint() constraint {
|
||||
return anyKind
|
||||
}
|
||||
|
||||
func (r *Comment) Kind() string {
|
||||
return tokCOMMENT
|
||||
func (r *Comment) Kind() Kind {
|
||||
return COMMENT
|
||||
}
|
||||
|
||||
type Abi struct {
|
||||
@ -65,7 +66,7 @@ type Abi struct {
|
||||
|
||||
func newAbi(rule []string) (Rule, error) {
|
||||
var magic bool
|
||||
if len(rule) > 0 && rule[0] == tokABI {
|
||||
if len(rule) > 0 && rule[0] == ABI.Tok() {
|
||||
rule = rule[1:]
|
||||
}
|
||||
if len(rule) != 1 {
|
||||
@ -113,8 +114,8 @@ func (r *Abi) Constraint() constraint {
|
||||
return preambleKind
|
||||
}
|
||||
|
||||
func (r *Abi) Kind() string {
|
||||
return tokABI
|
||||
func (r *Abi) Kind() Kind {
|
||||
return ABI
|
||||
}
|
||||
|
||||
type Alias struct {
|
||||
@ -124,7 +125,7 @@ type Alias struct {
|
||||
}
|
||||
|
||||
func newAlias(rule []string) (Rule, error) {
|
||||
if len(rule) > 0 && rule[0] == tokALIAS {
|
||||
if len(rule) > 0 && rule[0] == ALIAS.Tok() {
|
||||
rule = rule[1:]
|
||||
}
|
||||
if len(rule) != 3 {
|
||||
@ -165,8 +166,8 @@ func (r *Alias) Constraint() constraint {
|
||||
return preambleKind
|
||||
}
|
||||
|
||||
func (r *Alias) Kind() string {
|
||||
return tokALIAS
|
||||
func (r *Alias) Kind() Kind {
|
||||
return ALIAS
|
||||
}
|
||||
|
||||
type Include struct {
|
||||
@ -180,7 +181,7 @@ func newInclude(rule []string) (Rule, error) {
|
||||
var magic bool
|
||||
var ifexists bool
|
||||
|
||||
if len(rule) > 0 && rule[0] == tokINCLUDE {
|
||||
if len(rule) > 0 && rule[0] == INCLUDE.Tok() {
|
||||
rule = rule[1:]
|
||||
}
|
||||
|
||||
@ -239,8 +240,8 @@ func (r *Include) Constraint() constraint {
|
||||
return anyKind
|
||||
}
|
||||
|
||||
func (r *Include) Kind() string {
|
||||
return tokINCLUDE
|
||||
func (r *Include) Kind() Kind {
|
||||
return INCLUDE
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
@ -257,7 +258,7 @@ func newVariable(rule []string) (Rule, error) {
|
||||
return nil, fmt.Errorf("invalid variable format: %v", rule)
|
||||
}
|
||||
|
||||
name := strings.Trim(rule[0], tokVARIABLE+"}")
|
||||
name := strings.Trim(rule[0], VARIABLE.Tok()+"}")
|
||||
switch rule[1] {
|
||||
case tokEQUAL:
|
||||
define = true
|
||||
@ -297,13 +298,13 @@ func (r *Variable) Equals(other any) bool {
|
||||
}
|
||||
|
||||
func (r *Variable) String() string {
|
||||
return renderTemplate("variable", r)
|
||||
return renderTemplate(r.Kind(), r)
|
||||
}
|
||||
|
||||
func (r *Variable) Constraint() constraint {
|
||||
return preambleKind
|
||||
}
|
||||
|
||||
func (r *Variable) Kind() string {
|
||||
return tokVARIABLE
|
||||
func (r *Variable) Kind() Kind {
|
||||
return VARIABLE
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
PROFILE Kind = "profile"
|
||||
|
||||
tokATTRIBUTES = "xattrs"
|
||||
tokFLAGS = "flags"
|
||||
tokPROFILE = "profile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
requirements[tokPROFILE] = requirement{
|
||||
requirements[PROFILE] = requirement{
|
||||
tokFLAGS: {
|
||||
"enforce", "complain", "kill", "default_allow", "unconfined",
|
||||
"prompt", "audit", "mediate_deleted", "attach_disconnected",
|
||||
@ -52,7 +53,7 @@ func newHeader(rule []string) (Header, error) {
|
||||
if rule[len(rule)-1] == "{" {
|
||||
rule = rule[:len(rule)-1]
|
||||
}
|
||||
if rule[0] == tokPROFILE {
|
||||
if rule[0] == PROFILE.Tok() {
|
||||
rule = rule[1:]
|
||||
}
|
||||
|
||||
@ -120,8 +121,8 @@ func (p *Profile) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (p *Profile) Kind() string {
|
||||
return tokPROFILE
|
||||
func (p *Profile) Kind() Kind {
|
||||
return PROFILE
|
||||
}
|
||||
|
||||
// Merge merge similar rules together.
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokPTRACE = "ptrace"
|
||||
const PTRACE Kind = "ptrace"
|
||||
|
||||
func init() {
|
||||
requirements[tokPTRACE] = requirement{
|
||||
requirements[PTRACE] = requirement{
|
||||
"access": []string{
|
||||
"r", "w", "rw", "read", "readby", "trace", "tracedby",
|
||||
},
|
||||
@ -30,7 +30,7 @@ func newPtraceFromLog(log map[string]string) Rule {
|
||||
return &Ptrace{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Access: Must(toAccess(tokPTRACE, log["requested_mask"])),
|
||||
Access: Must(toAccess(PTRACE, log["requested_mask"])),
|
||||
Peer: log["peer"],
|
||||
}
|
||||
}
|
||||
@ -67,6 +67,6 @@ func (r *Ptrace) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Ptrace) Kind() string {
|
||||
return tokPTRACE
|
||||
func (r *Ptrace) Kind() Kind {
|
||||
return PTRACE
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func (f *AppArmorProfileFile) Resolve() error {
|
||||
}
|
||||
|
||||
func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) {
|
||||
if !strings.Contains(input, tokVARIABLE) {
|
||||
if !strings.Contains(input, VARIABLE.Tok()) {
|
||||
return []string{input}, nil
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) {
|
||||
if vrbl.Name == varname {
|
||||
found = true
|
||||
for _, v := range vrbl.Values {
|
||||
if strings.Contains(v, tokVARIABLE+varname+"}") {
|
||||
if strings.Contains(v, VARIABLE.Tok()+varname+"}") {
|
||||
return nil, fmt.Errorf("recursive variable found in: %s", varname)
|
||||
}
|
||||
newValues := strings.ReplaceAll(input, variable, v)
|
||||
@ -152,7 +152,7 @@ func (f *AppArmorProfileFile) resolveInclude(include *Include) error {
|
||||
}
|
||||
|
||||
// Remove all includes in iFile
|
||||
iFile.Preamble = iFile.Preamble.DeleteKind(tokINCLUDE)
|
||||
iFile.Preamble = iFile.Preamble.DeleteKind(INCLUDE)
|
||||
|
||||
// Cache the included file
|
||||
includeCache[include] = iFile
|
||||
|
@ -7,12 +7,11 @@ package aa
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
tokRLIMIT = "rlimit"
|
||||
tokSET = "set"
|
||||
RLIMIT Kind = "rlimit"
|
||||
)
|
||||
|
||||
func init() {
|
||||
requirements[tokRLIMIT] = requirement{
|
||||
requirements[RLIMIT] = requirement{
|
||||
"keys": {
|
||||
"cpu", "fsize", "data", "stack", "core", "rss", "nofile", "ofile",
|
||||
"as", "nproc", "memlock", "locks", "sigpending", "msgqueue", "nice",
|
||||
@ -68,6 +67,6 @@ func (r *Rlimit) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Rlimit) Kind() string {
|
||||
return tokRLIMIT
|
||||
func (r *Rlimit) Kind() Kind {
|
||||
return RLIMIT
|
||||
}
|
||||
|
@ -26,6 +26,20 @@ const (
|
||||
blockKind // The rule can only be found in a profile
|
||||
)
|
||||
|
||||
// Kind represents an AppArmor rule kind.
|
||||
type Kind string
|
||||
|
||||
func (k Kind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
func (k Kind) Tok() string {
|
||||
if t, ok := tok[k]; ok {
|
||||
return t
|
||||
}
|
||||
return string(k)
|
||||
}
|
||||
|
||||
// Rule generic interface for all AppArmor rules
|
||||
type Rule interface {
|
||||
Validate() error
|
||||
@ -33,7 +47,7 @@ type Rule interface {
|
||||
Equals(other any) bool
|
||||
String() string
|
||||
Constraint() constraint
|
||||
Kind() string
|
||||
Kind() Kind
|
||||
}
|
||||
|
||||
type Rules []Rule
|
||||
@ -77,7 +91,7 @@ func (r Rules) Delete(i int) Rules {
|
||||
return append(r[:i], r[i+1:]...)
|
||||
}
|
||||
|
||||
func (r Rules) DeleteKind(kind string) Rules {
|
||||
func (r Rules) DeleteKind(kind Kind) Rules {
|
||||
res := make(Rules, 0)
|
||||
for _, rule := range r {
|
||||
if rule.Kind() != kind {
|
||||
@ -87,7 +101,7 @@ func (r Rules) DeleteKind(kind string) Rules {
|
||||
return res
|
||||
}
|
||||
|
||||
func (r Rules) Filter(filter string) Rules {
|
||||
func (r Rules) Filter(filter Kind) Rules {
|
||||
res := make(Rules, 0)
|
||||
for _, rule := range r {
|
||||
if rule.Kind() != filter {
|
||||
@ -128,12 +142,12 @@ func Must[T any](v T, err error) T {
|
||||
return v
|
||||
}
|
||||
|
||||
func validateValues(rule string, key string, values []string) error {
|
||||
func validateValues(kind Kind, key string, values []string) error {
|
||||
for _, v := range values {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(requirements[rule][key], v) {
|
||||
if !slices.Contains(requirements[kind][key], v) {
|
||||
return fmt.Errorf("invalid mode '%s'", v)
|
||||
}
|
||||
}
|
||||
@ -142,10 +156,10 @@ func validateValues(rule string, key string, values []string) error {
|
||||
|
||||
// Helper function to convert a string to a slice of rule values according to
|
||||
// the rule requirements as defined in the requirements map.
|
||||
func toValues(rule string, key string, input string) ([]string, error) {
|
||||
req, ok := requirements[rule][key]
|
||||
func toValues(kind Kind, key string, input string) ([]string, error) {
|
||||
req, ok := requirements[kind][key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unrecognized requirement '%s' for rule %s", key, rule)
|
||||
return nil, fmt.Errorf("unrecognized requirement '%s' for rule %s", key, kind)
|
||||
}
|
||||
|
||||
res := tokenToSlice(input)
|
||||
@ -156,22 +170,22 @@ func toValues(rule string, key string, input string) ([]string, error) {
|
||||
}
|
||||
}
|
||||
slices.SortFunc(res, func(i, j string) int {
|
||||
return requirementsWeights[rule][key][i] - requirementsWeights[rule][key][j]
|
||||
return requirementsWeights[kind][key][i] - requirementsWeights[kind][key][j]
|
||||
})
|
||||
return slices.Compact(res), nil
|
||||
}
|
||||
|
||||
// Helper function to convert an access string to a slice of access according to
|
||||
// the rule requirements as defined in the requirements map.
|
||||
func toAccess(rule string, input string) ([]string, error) {
|
||||
func toAccess(kind Kind, input string) ([]string, error) {
|
||||
var res []string
|
||||
|
||||
switch rule {
|
||||
case tokFILE:
|
||||
switch kind {
|
||||
case FILE:
|
||||
raw := strings.Split(input, "")
|
||||
trans := []string{}
|
||||
for _, access := range raw {
|
||||
if slices.Contains(requirements[tokFILE]["access"], access) {
|
||||
if slices.Contains(requirements[FILE]["access"], access) {
|
||||
res = append(res, access)
|
||||
} else {
|
||||
trans = append(trans, access)
|
||||
@ -180,17 +194,17 @@ func toAccess(rule string, input string) ([]string, error) {
|
||||
|
||||
transition := strings.Join(trans, "")
|
||||
if len(transition) > 0 {
|
||||
if slices.Contains(requirements[tokFILE]["transition"], transition) {
|
||||
if slices.Contains(requirements[FILE]["transition"], transition) {
|
||||
res = append(res, transition)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unrecognized transition: %s", transition)
|
||||
}
|
||||
}
|
||||
|
||||
case tokFILE + "-log":
|
||||
case FILE + "-log":
|
||||
raw := strings.Split(input, "")
|
||||
for _, access := range raw {
|
||||
if slices.Contains(requirements[tokFILE]["access"], access) {
|
||||
if slices.Contains(requirements[FILE]["access"], access) {
|
||||
res = append(res, access)
|
||||
} else if maskToAccess[access] != "" {
|
||||
res = append(res, maskToAccess[access])
|
||||
@ -200,7 +214,7 @@ func toAccess(rule string, input string) ([]string, error) {
|
||||
}
|
||||
|
||||
default:
|
||||
return toValues(rule, "access", input)
|
||||
return toValues(kind, "access", input)
|
||||
}
|
||||
|
||||
slices.SortFunc(res, cmpFileAccess)
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokSIGNAL = "signal"
|
||||
const SIGNAL Kind = "signal"
|
||||
|
||||
func init() {
|
||||
requirements[tokSIGNAL] = requirement{
|
||||
requirements[SIGNAL] = requirement{
|
||||
"access": {
|
||||
"r", "w", "rw", "read", "write", "send", "receive",
|
||||
},
|
||||
@ -44,7 +44,7 @@ func newSignalFromLog(log map[string]string) Rule {
|
||||
return &Signal{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Access: Must(toAccess(tokSIGNAL, log["requested_mask"])),
|
||||
Access: Must(toAccess(SIGNAL, log["requested_mask"])),
|
||||
Set: []string{log["signal"]},
|
||||
Peer: log["peer"],
|
||||
}
|
||||
@ -88,6 +88,6 @@ func (r *Signal) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Signal) Kind() string {
|
||||
return tokSIGNAL
|
||||
func (r *Signal) Kind() Kind {
|
||||
return SIGNAL
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ var (
|
||||
|
||||
// The functions available in the template
|
||||
tmplFunctionMap = template.FuncMap{
|
||||
"typeof": typeOf,
|
||||
"kindof": kindOf,
|
||||
"join": join,
|
||||
"cjoin": cjoin,
|
||||
"indent": indent,
|
||||
@ -34,24 +34,25 @@ var (
|
||||
}
|
||||
|
||||
// The apparmor templates
|
||||
tmpl = generateTemplates([]string{
|
||||
tmpl = generateTemplates([]Kind{
|
||||
// Global templates
|
||||
"apparmor",
|
||||
tokPROFILE,
|
||||
PROFILE,
|
||||
HAT,
|
||||
"rules",
|
||||
|
||||
// Preamble templates
|
||||
tokABI,
|
||||
tokALIAS,
|
||||
tokINCLUDE,
|
||||
"variable",
|
||||
"comment",
|
||||
ABI,
|
||||
ALIAS,
|
||||
INCLUDE,
|
||||
VARIABLE,
|
||||
COMMENT,
|
||||
|
||||
// Rules templates
|
||||
tokALL, tokRLIMIT, tokUSERNS, tokCAPABILITY, tokNETWORK,
|
||||
tokMOUNT, tokREMOUNT, tokUMOUNT, tokPIVOTROOT, tokCHANGEPROFILE,
|
||||
tokMQUEUE, tokIOURING, tokUNIX, tokPTRACE, tokSIGNAL, tokDBUS,
|
||||
tokFILE, tokLINK,
|
||||
ALL, RLIMIT, USERNS, CAPABILITY, NETWORK,
|
||||
MOUNT, REMOUNT, UMOUNT, PIVOTROOT, CHANGEPROFILE,
|
||||
MQUEUE, IOURING, UNIX, PTRACE, SIGNAL, DBUS,
|
||||
FILE, LINK,
|
||||
})
|
||||
|
||||
// convert apparmor requested mask to apparmor access mode
|
||||
@ -64,27 +65,28 @@ var (
|
||||
}
|
||||
|
||||
// The order the apparmor rules should be sorted
|
||||
ruleAlphabet = []string{
|
||||
"include",
|
||||
"all",
|
||||
"rlimit",
|
||||
"userns",
|
||||
"capability",
|
||||
"network",
|
||||
"mount",
|
||||
"remount",
|
||||
"umount",
|
||||
"pivotroot",
|
||||
"changeprofile",
|
||||
"mqueue",
|
||||
"iouring",
|
||||
"signal",
|
||||
"ptrace",
|
||||
"unix",
|
||||
"dbus",
|
||||
"file",
|
||||
"link",
|
||||
"profile",
|
||||
ruleAlphabet = []Kind{
|
||||
INCLUDE,
|
||||
ALL,
|
||||
RLIMIT,
|
||||
USERNS,
|
||||
CAPABILITY,
|
||||
NETWORK,
|
||||
MOUNT,
|
||||
REMOUNT,
|
||||
UMOUNT,
|
||||
PIVOTROOT,
|
||||
CHANGEPROFILE,
|
||||
MQUEUE,
|
||||
IOURING,
|
||||
SIGNAL,
|
||||
PTRACE,
|
||||
UNIX,
|
||||
DBUS,
|
||||
FILE,
|
||||
LINK,
|
||||
PROFILE,
|
||||
HAT,
|
||||
"include_if_exists",
|
||||
}
|
||||
ruleWeights = generateWeights(ruleAlphabet)
|
||||
@ -117,16 +119,16 @@ var (
|
||||
fileWeights = generateWeights(fileAlphabet)
|
||||
|
||||
// The order the rule values (access, type, domains, etc) should be sorted
|
||||
requirements = map[string]requirement{}
|
||||
requirementsWeights map[string]map[string]map[string]int
|
||||
requirements = map[Kind]requirement{}
|
||||
requirementsWeights map[Kind]map[string]map[string]int
|
||||
)
|
||||
|
||||
func init() {
|
||||
requirementsWeights = generateRequirementsWeights(requirements)
|
||||
}
|
||||
|
||||
func generateTemplates(names []string) map[string]*template.Template {
|
||||
res := make(map[string]*template.Template, len(names))
|
||||
func generateTemplates(names []Kind) map[Kind]*template.Template {
|
||||
res := make(map[Kind]*template.Template, len(names))
|
||||
base := template.New("").Funcs(tmplFunctionMap)
|
||||
base = template.Must(base.ParseFS(tmplFiles,
|
||||
"templates/*.j2", "templates/rule/*.j2",
|
||||
@ -141,11 +143,11 @@ func generateTemplates(names []string) map[string]*template.Template {
|
||||
return res
|
||||
}
|
||||
|
||||
func renderTemplate(name string, data any) string {
|
||||
func renderTemplate(name Kind, data any) string {
|
||||
var res strings.Builder
|
||||
template, ok := tmpl[name]
|
||||
if !ok {
|
||||
panic("template '" + name + "' not found")
|
||||
panic("template '" + name.String() + "' not found")
|
||||
}
|
||||
err := template.Execute(&res, data)
|
||||
if err != nil {
|
||||
@ -154,16 +156,16 @@ func renderTemplate(name string, data any) string {
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func generateWeights(alphabet []string) map[string]int {
|
||||
res := make(map[string]int, len(alphabet))
|
||||
func generateWeights[T Kind | string](alphabet []T) map[T]int {
|
||||
res := make(map[T]int, len(alphabet))
|
||||
for i, r := range alphabet {
|
||||
res[r] = i
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func generateRequirementsWeights(requirements map[string]requirement) map[string]map[string]map[string]int {
|
||||
res := make(map[string]map[string]map[string]int, len(requirements))
|
||||
func generateRequirementsWeights(requirements map[Kind]requirement) map[Kind]map[string]map[string]int {
|
||||
res := make(map[Kind]map[string]map[string]int, len(requirements))
|
||||
for rule, req := range requirements {
|
||||
res[rule] = make(map[string]map[string]int, len(req))
|
||||
for key, values := range req {
|
||||
@ -207,15 +209,11 @@ func cjoin(i any) string {
|
||||
}
|
||||
}
|
||||
|
||||
func typeOf(i any) string {
|
||||
func kindOf(i any) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(reflect.TypeOf(i).String(), "*aa.")
|
||||
}
|
||||
|
||||
func typeToValue(i reflect.Type) string {
|
||||
return strings.ToLower(strings.TrimPrefix(i.String(), "*aa."))
|
||||
return i.(Rule).Kind().String()
|
||||
}
|
||||
|
||||
func setindent(i string) string {
|
||||
|
@ -4,118 +4,118 @@
|
||||
|
||||
{{- define "rules" -}}
|
||||
|
||||
{{- $oldtype := "" -}}
|
||||
{{- $oldkind := "" -}}
|
||||
{{- range . -}}
|
||||
{{- $type := typeof . -}}
|
||||
{{- if eq $type "" -}}
|
||||
{{- $kind := kindof . -}}
|
||||
{{- if eq $kind "" -}}
|
||||
{{- "\n" -}}
|
||||
{{- continue -}}
|
||||
{{- end -}}
|
||||
{{- if eq $type "Comment" -}}
|
||||
{{- if eq $kind "comment" -}}
|
||||
{{- template "comment" . -}}
|
||||
{{- "\n" -}}
|
||||
{{- continue -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and (ne $type $oldtype) (ne $oldtype "") -}}
|
||||
{{- if and (ne $kind $oldkind) (ne $oldkind "") -}}
|
||||
{{- "\n" -}}
|
||||
{{- end -}}
|
||||
{{- indent "" -}}
|
||||
|
||||
{{- if eq $type "Abi" -}}
|
||||
{{- if eq $kind "abi" -}}
|
||||
{{- template "abi" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Alias" -}}
|
||||
{{- if eq $kind "alias" -}}
|
||||
{{- template "alias" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Include" -}}
|
||||
{{- if eq $kind "include" -}}
|
||||
{{- template "include" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Variable" -}}
|
||||
{{- if eq $kind "variable" -}}
|
||||
{{- template "variable" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "All" -}}
|
||||
{{- if eq $kind "all" -}}
|
||||
{{- template "all" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Rlimit" -}}
|
||||
{{- if eq $kind "rlimit" -}}
|
||||
{{- template "rlimit" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Userns" -}}
|
||||
{{- if eq $kind "userns" -}}
|
||||
{{- template "userns" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Capability" -}}
|
||||
{{- if eq $kind "capability" -}}
|
||||
{{- template "capability" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Network" -}}
|
||||
{{- if eq $kind "network" -}}
|
||||
{{- template "network" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Mount" -}}
|
||||
{{- if eq $kind "mount" -}}
|
||||
{{- template "mount" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Remount" -}}
|
||||
{{- if eq $kind "remount" -}}
|
||||
{{- template "remount" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Umount" -}}
|
||||
{{- if eq $kind "umount" -}}
|
||||
{{- template "umount" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "PivotRoot" -}}
|
||||
{{- if eq $kind "pivot_root" -}}
|
||||
{{- template "pivot_root" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "ChangeProfile" -}}
|
||||
{{- if eq $kind "change_profile" -}}
|
||||
{{- template "change_profile" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Mqueue" -}}
|
||||
{{- if eq $kind "mqueue" -}}
|
||||
{{- template "mqueue" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "IOUring" -}}
|
||||
{{- if eq $kind "io_uring" -}}
|
||||
{{- template "io_uring" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Unix" -}}
|
||||
{{- if eq $kind "unix" -}}
|
||||
{{- template "unix" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Ptrace" -}}
|
||||
{{- if eq $kind "ptrace" -}}
|
||||
{{- template "ptrace" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Signal" -}}
|
||||
{{- if eq $kind "signal" -}}
|
||||
{{- template "signal" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Dbus" -}}
|
||||
{{- if eq $kind "dbus" -}}
|
||||
{{- template "dbus" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "File" -}}
|
||||
{{- if eq $kind "file" -}}
|
||||
{{- template "file" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Link" -}}
|
||||
{{- if eq $kind "link" -}}
|
||||
{{- template "link" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $type "Profile" -}}
|
||||
{{- if eq $kind "profile" -}}
|
||||
{{- template "profile" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- "\n" -}}
|
||||
{{- $oldtype = $type -}}
|
||||
{{- $oldkind = $kind -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
|
@ -9,10 +9,10 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const tokUNIX = "unix"
|
||||
const UNIX Kind = "unix"
|
||||
|
||||
func init() {
|
||||
requirements[tokUNIX] = requirement{
|
||||
requirements[UNIX] = requirement{
|
||||
"access": []string{
|
||||
"create", "bind", "listen", "accept", "connect", "shutdown",
|
||||
"getattr", "setattr", "getopt", "setopt", "send", "receive",
|
||||
@ -39,7 +39,7 @@ func newUnixFromLog(log map[string]string) Rule {
|
||||
return &Unix{
|
||||
RuleBase: newRuleFromLog(log),
|
||||
Qualifier: newQualifierFromLog(log),
|
||||
Access: Must(toAccess(tokUNIX, log["requested_mask"])),
|
||||
Access: Must(toAccess(UNIX, log["requested_mask"])),
|
||||
Type: log["sock_type"],
|
||||
Protocol: log["protocol"],
|
||||
Address: log["addr"],
|
||||
@ -107,6 +107,6 @@ func (r *Unix) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Unix) Kind() string {
|
||||
return tokUNIX
|
||||
func (r *Unix) Kind() Kind {
|
||||
return UNIX
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
package aa
|
||||
|
||||
const tokUSERNS = "userns"
|
||||
const USERNS Kind = "userns"
|
||||
|
||||
type Userns struct {
|
||||
RuleBase
|
||||
@ -45,6 +45,6 @@ func (r *Userns) Constraint() constraint {
|
||||
return blockKind
|
||||
}
|
||||
|
||||
func (r *Userns) Kind() string {
|
||||
return tokUSERNS
|
||||
func (r *Userns) Kind() Kind {
|
||||
return USERNS
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user