gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snapd-apparmor/main.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // This tool is provided for integration with systemd on distributions where 21 // apparmor profiles generated and managed by snapd are not loaded by the 22 // system-wide apparmor systemd integration on early boot-up. 23 // 24 // Only the start operation is provided as all other activity is managed by 25 // snapd as a part of the life-cycle of particular snaps. 26 // 27 // In addition this tool assumes that the system-wide apparmor service has 28 // already executed, initializing apparmor file-systems as necessary. 29 // 30 // NOTE: This tool ignores failures in some scenarios as the intent is to 31 // simply load application profiles ahead of time, as many as we can (for 32 // performance reasons), even if for whatever reason some of those fail. 33 34 package main 35 36 import ( 37 "errors" 38 "fmt" 39 "io/ioutil" 40 "os" 41 "os/exec" 42 "path/filepath" 43 "strings" 44 45 "gitee.com/mysnapcore/mysnapd/dirs" 46 "gitee.com/mysnapcore/mysnapd/logger" 47 "gitee.com/mysnapcore/mysnapd/release" 48 apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor" 49 "gitee.com/mysnapcore/mysnapd/snapdtool" 50 ) 51 52 // Checks to see if the current container is capable of having internal AppArmor 53 // profiles that should be loaded. 54 // 55 // The only known container environments capable of supporting internal policy 56 // are LXD and LXC environment. 57 // 58 // Returns true if the container environment is capable of having its own internal 59 // policy and false otherwise. 60 // 61 // IMPORTANT: This function will return true in the case of a non-LXD/non-LXC 62 // system container technology being nested inside of a LXD/LXC container that 63 // utilized an AppArmor namespace and profile stacking. The reason true will be 64 // returned is because .ns_stacked will be "yes" and .ns_name will still match 65 // "lx[dc]-*" since the nested system container technology will not have set up 66 // a new AppArmor profile namespace. This will result in the nested system 67 // container's boot process to experience failed policy loads but the boot 68 // process should continue without any loss of functionality. This is an 69 // unsupported configuration that cannot be properly handled by this function. 70 // 71 func isContainerWithInternalPolicy() bool { 72 if release.OnWSL { 73 return true 74 } 75 76 var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor") 77 var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked") 78 var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name") 79 80 contents, err := ioutil.ReadFile(nsStackedPath) 81 if err != nil && !errors.Is(err, os.ErrNotExist) { 82 logger.Noticef("Failed to read %s: %v", nsStackedPath, err) 83 return false 84 } 85 86 if strings.TrimSpace(string(contents)) != "yes" { 87 return false 88 } 89 90 contents, err = ioutil.ReadFile(nsNamePath) 91 if err != nil && !errors.Is(err, os.ErrNotExist) { 92 logger.Noticef("Failed to read %s: %v", nsNamePath, err) 93 return false 94 } 95 96 // LXD and LXC set up AppArmor namespaces starting with "lxd-" and 97 // "lxc-", respectively. Return false for all other namespace 98 // identifiers. 99 name := strings.TrimSpace(string(contents)) 100 if !strings.HasPrefix(name, "lxd-") && !strings.HasPrefix(name, "lxc-") { 101 return false 102 } 103 return true 104 } 105 106 func loadAppArmorProfiles() error { 107 candidates, err := filepath.Glob(dirs.SnapAppArmorDir + "/*") 108 if err != nil { 109 return fmt.Errorf("Failed to glob profiles from snap apparmor dir %s: %v", dirs.SnapAppArmorDir, err) 110 } 111 112 profiles := make([]string, 0, len(candidates)) 113 for _, profile := range candidates { 114 // Filter out profiles with names ending with ~, those are 115 // temporary files created by snapd. 116 if strings.HasSuffix(profile, "~") { 117 continue 118 } 119 profiles = append(profiles, profile) 120 } 121 if len(profiles) == 0 { 122 logger.Noticef("No profiles to load") 123 return nil 124 } 125 logger.Noticef("Loading profiles %v", profiles) 126 return apparmor_sandbox.LoadProfiles(profiles, apparmor_sandbox.SystemCacheDir, 0) 127 } 128 129 func isContainer() bool { 130 // systemd's implementation may fail on WSL2 with custom kernels 131 return release.OnWSL || (exec.Command("systemd-detect-virt", "--quiet", "--container").Run() == nil) 132 } 133 134 func validateArgs(args []string) error { 135 if len(args) != 1 || args[0] != "start" { 136 return errors.New("Expected to be called with a single 'start' argument.") 137 } 138 return nil 139 } 140 141 func main() { 142 if err := run(); err != nil { 143 fmt.Fprintf(os.Stderr, "error: %v\n", err) 144 os.Exit(1) 145 } 146 } 147 148 func run() error { 149 snapdtool.ExecInSnapdOrCoreSnap() 150 151 if err := validateArgs(os.Args[1:]); err != nil { 152 return err 153 } 154 155 if isContainer() { 156 logger.Debugf("inside container environment") 157 // in container environment - see if container has own 158 // policy that we need to manage otherwise get out of the 159 // way 160 if !isContainerWithInternalPolicy() { 161 logger.Noticef("Inside container environment without internal policy") 162 return nil 163 } 164 } 165 166 return loadAppArmorProfiles() 167 }