github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/apparmor/apparmor_linux.go (about) 1 // +build linux,apparmor 2 3 package apparmor 4 5 import ( 6 "bufio" 7 "bytes" 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 "path" 13 "strconv" 14 "strings" 15 "text/template" 16 17 "github.com/containers/libpod/pkg/rootless" 18 runcaa "github.com/opencontainers/runc/libcontainer/apparmor" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 ) 22 23 // profileDirectory is the file store for apparmor profiles and macros. 24 var profileDirectory = "/etc/apparmor.d" 25 26 // IsEnabled returns true if AppArmor is enabled on the host. 27 func IsEnabled() bool { 28 if rootless.IsRootless() { 29 return false 30 } 31 return runcaa.IsEnabled() 32 } 33 34 // profileData holds information about the given profile for generation. 35 type profileData struct { 36 // Name is profile name. 37 Name string 38 // Imports defines the apparmor functions to import, before defining the profile. 39 Imports []string 40 // InnerImports defines the apparmor functions to import in the profile. 41 InnerImports []string 42 // Version is the {major, minor, patch} version of apparmor_parser as a single number. 43 Version int 44 } 45 46 // generateDefault creates an apparmor profile from ProfileData. 47 func (p *profileData) generateDefault(out io.Writer) error { 48 compiled, err := template.New("apparmor_profile").Parse(libpodProfileTemplate) 49 if err != nil { 50 return err 51 } 52 53 if macroExists("tunables/global") { 54 p.Imports = append(p.Imports, "#include <tunables/global>") 55 } else { 56 p.Imports = append(p.Imports, "@{PROC}=/proc/") 57 } 58 59 if macroExists("abstractions/base") { 60 p.InnerImports = append(p.InnerImports, "#include <abstractions/base>") 61 } 62 63 ver, err := getAAParserVersion() 64 if err != nil { 65 return err 66 } 67 p.Version = ver 68 69 return compiled.Execute(out, p) 70 } 71 72 // macrosExists checks if the passed macro exists. 73 func macroExists(m string) bool { 74 _, err := os.Stat(path.Join(profileDirectory, m)) 75 return err == nil 76 } 77 78 // InstallDefault generates a default profile and loads it into the kernel 79 // using 'apparmor_parser'. 80 func InstallDefault(name string) error { 81 if rootless.IsRootless() { 82 return ErrApparmorRootless 83 } 84 85 p := profileData{ 86 Name: name, 87 } 88 89 cmd := exec.Command("apparmor_parser", "-Kr") 90 pipe, err := cmd.StdinPipe() 91 if err != nil { 92 return err 93 } 94 if err := cmd.Start(); err != nil { 95 if pipeErr := pipe.Close(); pipeErr != nil { 96 logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) 97 } 98 return err 99 } 100 if err := p.generateDefault(pipe); err != nil { 101 if pipeErr := pipe.Close(); pipeErr != nil { 102 logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) 103 } 104 if cmdErr := cmd.Wait(); cmdErr != nil { 105 logrus.Errorf("unable to wait for apparmor command: %q", cmdErr) 106 } 107 return err 108 } 109 110 if pipeErr := pipe.Close(); pipeErr != nil { 111 logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) 112 } 113 return cmd.Wait() 114 } 115 116 // DefaultContent returns the default profile content as byte slice. The 117 // profile is named as the provided `name`. The function errors if the profile 118 // generation fails. 119 func DefaultContent(name string) ([]byte, error) { 120 p := profileData{Name: name} 121 var bytes bytes.Buffer 122 if err := p.generateDefault(&bytes); err != nil { 123 return nil, err 124 } 125 return bytes.Bytes(), nil 126 } 127 128 // IsLoaded checks if a profile with the given name has been loaded into the 129 // kernel. 130 func IsLoaded(name string) (bool, error) { 131 if name != "" && rootless.IsRootless() { 132 return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) 133 } 134 135 file, err := os.Open("/sys/kernel/security/apparmor/profiles") 136 if err != nil { 137 if os.IsNotExist(err) { 138 return false, nil 139 } 140 return false, err 141 } 142 defer file.Close() 143 144 r := bufio.NewReader(file) 145 for { 146 p, err := r.ReadString('\n') 147 if err == io.EOF { 148 break 149 } 150 if err != nil { 151 return false, err 152 } 153 if strings.HasPrefix(p, name+" ") { 154 return true, nil 155 } 156 } 157 158 return false, nil 159 } 160 161 // execAAParser runs `apparmor_parser` with the passed arguments. 162 func execAAParser(dir string, args ...string) (string, error) { 163 c := exec.Command("apparmor_parser", args...) 164 c.Dir = dir 165 166 output, err := c.CombinedOutput() 167 if err != nil { 168 return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err) 169 } 170 171 return string(output), nil 172 } 173 174 // getAAParserVersion returns the major and minor version of apparmor_parser. 175 func getAAParserVersion() (int, error) { 176 output, err := execAAParser("", "--version") 177 if err != nil { 178 return -1, err 179 } 180 return parseAAParserVersion(output) 181 } 182 183 // parseAAParserVersion parses the given `apparmor_parser --version` output and 184 // returns the major and minor version number as an integer. 185 func parseAAParserVersion(output string) (int, error) { 186 // output is in the form of the following: 187 // AppArmor parser version 2.9.1 188 // Copyright (C) 1999-2008 Novell Inc. 189 // Copyright 2009-2012 Canonical Ltd. 190 lines := strings.SplitN(output, "\n", 2) 191 words := strings.Split(lines[0], " ") 192 version := words[len(words)-1] 193 194 // split by major minor version 195 v := strings.Split(version, ".") 196 if len(v) == 0 || len(v) > 3 { 197 return -1, fmt.Errorf("parsing version failed for output: `%s`", output) 198 } 199 200 // Default the versions to 0. 201 var majorVersion, minorVersion, patchLevel int 202 203 majorVersion, err := strconv.Atoi(v[0]) 204 if err != nil { 205 return -1, err 206 } 207 208 if len(v) > 1 { 209 minorVersion, err = strconv.Atoi(v[1]) 210 if err != nil { 211 return -1, err 212 } 213 } 214 if len(v) > 2 { 215 patchLevel, err = strconv.Atoi(v[2]) 216 if err != nil { 217 return -1, err 218 } 219 } 220 221 // major*10^5 + minor*10^3 + patch*10^0 222 numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel 223 return numericVersion, nil 224 225 } 226 227 // CheckProfileAndLoadDefault checks if the specified profile is loaded and 228 // loads the DefaultLibpodProfile if the specified on is prefixed by 229 // DefaultLipodProfilePrefix. This allows to always load and apply the latest 230 // default AppArmor profile. Note that AppArmor requires root. If it's a 231 // default profile, return DefaultLipodProfilePrefix, otherwise the specified 232 // one. 233 func CheckProfileAndLoadDefault(name string) (string, error) { 234 if name == "unconfined" { 235 return name, nil 236 } 237 238 // AppArmor is not supported in rootless mode as it requires root 239 // privileges. Return an error in case a specific profile is specified. 240 if rootless.IsRootless() { 241 if name != "" { 242 return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) 243 } else { 244 logrus.Debug("skipping loading default AppArmor profile (rootless mode)") 245 return "", nil 246 } 247 } 248 249 // Check if AppArmor is disabled and error out if a profile is to be set. 250 if !runcaa.IsEnabled() { 251 if name == "" { 252 return "", nil 253 } else { 254 return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) 255 } 256 } 257 258 // If the specified name is not empty or is not a default libpod one, 259 // ignore it and return the name. 260 if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) { 261 isLoaded, err := IsLoaded(name) 262 if err != nil { 263 return "", err 264 } 265 if !isLoaded { 266 return "", fmt.Errorf("AppArmor profile %q specified but not loaded", name) 267 } 268 return name, nil 269 } 270 271 name = DefaultLibpodProfile 272 // To avoid expensive redundant loads on each invocation, check 273 // if it's loaded before installing it. 274 isLoaded, err := IsLoaded(name) 275 if err != nil { 276 return "", err 277 } 278 if !isLoaded { 279 err = InstallDefault(name) 280 if err != nil { 281 return "", err 282 } 283 logrus.Infof("successfully loaded AppAmor profile %q", name) 284 } else { 285 logrus.Infof("AppAmor profile %q is already loaded", name) 286 } 287 288 return name, nil 289 }