mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2024-11-14 23:43:56 +01:00
245 lines
5.3 KiB
Go
245 lines
5.3 KiB
Go
// apparmor.d - Full set of apparmor profiles
|
|
// Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
package aa
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
var (
|
|
// Default indentation for apparmor profile (2 spaces)
|
|
Indentation = " "
|
|
|
|
// The current indentation level
|
|
IndentationLevel = 0
|
|
|
|
//go:embed templates/*.j2
|
|
//go:embed templates/rule/*.j2
|
|
tmplFiles embed.FS
|
|
|
|
// The functions available in the template
|
|
tmplFunctionMap = template.FuncMap{
|
|
"kindof": kindOf,
|
|
"join": join,
|
|
"cjoin": cjoin,
|
|
"indent": indent,
|
|
"overindent": indentDbus,
|
|
"setindent": setindent,
|
|
}
|
|
|
|
// The apparmor templates
|
|
tmpl = generateTemplates([]Kind{
|
|
// Global templates
|
|
"apparmor",
|
|
PROFILE,
|
|
HAT,
|
|
"rules",
|
|
|
|
// Preamble templates
|
|
ABI,
|
|
ALIAS,
|
|
INCLUDE,
|
|
VARIABLE,
|
|
COMMENT,
|
|
|
|
// Rules templates
|
|
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
|
|
maskToAccess = map[string]string{
|
|
"a": "w",
|
|
"c": "w",
|
|
"d": "w",
|
|
"wc": "w",
|
|
"x": "ix",
|
|
}
|
|
|
|
// The order the apparmor rules should be sorted
|
|
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)
|
|
|
|
// The order the apparmor file rules should be sorted
|
|
fileAlphabet = []string{
|
|
"@{exec_path}", // 1. entry point
|
|
"@{sh_path}", // 2.1 shells
|
|
"@{bin}", // 2.1 binaries
|
|
"@{lib}", // 2.2 libraries
|
|
"/opt", // 2.3 opt binaries & libraries
|
|
"/usr/share", // 3. shared data
|
|
"/etc", // 4. system configuration
|
|
"/var", // 5.1 system read/write data
|
|
"/boot", // 5.2 boot files
|
|
"/home", // 6.1 user data
|
|
"@{HOME}", // 6.2 home files
|
|
"@{user_cache_dirs}", // 7.1 user caches
|
|
"@{user_config_dirs}", // 7.2 user config
|
|
"@{user_share_dirs}", // 7.3 user shared
|
|
"/tmp", // 8.1 Temporary data
|
|
"@{run}", // 8.2 Runtime data
|
|
"/dev/shm", // 8.3 Shared memory
|
|
"@{sys}", // 9. Sys files
|
|
"@{PROC}", // 10. Proc files
|
|
"/dev", // 11. Dev files
|
|
"deny", // 12. Deny rules
|
|
"profile", // 13. Subprofiles
|
|
}
|
|
fileWeights = generateWeights(fileAlphabet)
|
|
|
|
// The order the rule values (access, type, domains, etc) should be sorted
|
|
requirements = map[Kind]requirement{}
|
|
requirementsWeights map[Kind]map[string]map[string]int
|
|
)
|
|
|
|
func init() {
|
|
requirementsWeights = generateRequirementsWeights(requirements)
|
|
}
|
|
|
|
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",
|
|
))
|
|
for _, name := range names {
|
|
t := template.Must(base.Clone())
|
|
t = template.Must(t.Parse(
|
|
fmt.Sprintf(`{{- template "%s" . -}}`, name),
|
|
))
|
|
res[name] = t
|
|
}
|
|
return res
|
|
}
|
|
|
|
func renderTemplate(name Kind, data any) string {
|
|
var res strings.Builder
|
|
template, ok := tmpl[name]
|
|
if !ok {
|
|
panic("template '" + name.String() + "' not found")
|
|
}
|
|
err := template.Execute(&res, data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res.String()
|
|
}
|
|
|
|
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[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 {
|
|
res[rule][key] = generateWeights(values)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func join(i any) string {
|
|
switch reflect.TypeOf(i).Kind() {
|
|
case reflect.Slice:
|
|
return strings.Join(i.([]string), " ")
|
|
case reflect.Map:
|
|
res := []string{}
|
|
for k, v := range i.(map[string]string) {
|
|
res = append(res, k+"="+v)
|
|
}
|
|
return strings.Join(res, " ")
|
|
default:
|
|
return i.(string)
|
|
}
|
|
}
|
|
|
|
func cjoin(i any) string {
|
|
switch reflect.TypeOf(i).Kind() {
|
|
case reflect.Slice:
|
|
s := i.([]string)
|
|
if len(s) == 1 {
|
|
return s[0]
|
|
}
|
|
return "(" + strings.Join(s, " ") + ")"
|
|
case reflect.Map:
|
|
res := []string{}
|
|
for k, v := range i.(map[string]string) {
|
|
res = append(res, k+"="+v)
|
|
}
|
|
return "(" + strings.Join(res, " ") + ")"
|
|
default:
|
|
return i.(string)
|
|
}
|
|
}
|
|
|
|
func kindOf(i any) string {
|
|
if i == nil {
|
|
return ""
|
|
}
|
|
return i.(Rule).Kind().String()
|
|
}
|
|
|
|
func setindent(i string) string {
|
|
switch i {
|
|
case "++":
|
|
IndentationLevel++
|
|
case "--":
|
|
IndentationLevel--
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func indent(s string) string {
|
|
return strings.Repeat(Indentation, IndentationLevel) + s
|
|
}
|
|
|
|
func indentDbus(s string) string {
|
|
return strings.Join([]string{Indentation, s}, " ")
|
|
}
|
|
|
|
func getLetterIn(alphabet []string, in string) string {
|
|
for _, letter := range alphabet {
|
|
if strings.HasPrefix(in, letter) {
|
|
return letter
|
|
}
|
|
}
|
|
return ""
|
|
}
|