github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/sandbox/apparmor/apparmor_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 package apparmor_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "testing" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/sandbox/apparmor" 33 "github.com/snapcore/snapd/testutil" 34 ) 35 36 func TestApparmor(t *testing.T) { 37 TestingT(t) 38 } 39 40 type apparmorSuite struct{} 41 42 var _ = Suite(&apparmorSuite{}) 43 44 func (*apparmorSuite) TestAppArmorLevelTypeStringer(c *C) { 45 c.Check(apparmor.Unknown.String(), Equals, "unknown") 46 c.Check(apparmor.Unsupported.String(), Equals, "none") 47 c.Check(apparmor.Unusable.String(), Equals, "unusable") 48 c.Check(apparmor.Partial.String(), Equals, "partial") 49 c.Check(apparmor.Full.String(), Equals, "full") 50 c.Check(apparmor.LevelType(42).String(), Equals, "AppArmorLevelType:42") 51 } 52 53 func (*apparmorSuite) TestAppArmorSystemCacheFallsback(c *C) { 54 // if we create the system cache dir under a new rootdir, then the 55 // SystemCacheDir should take that value 56 dir1 := c.MkDir() 57 systemCacheDir := filepath.Join(dir1, "/etc/apparmor.d/cache") 58 err := os.MkdirAll(systemCacheDir, 0755) 59 c.Assert(err, IsNil) 60 dirs.SetRootDir(dir1) 61 c.Assert(apparmor.SystemCacheDir, Equals, systemCacheDir) 62 63 // but if we set a new root dir without the system cache dir, now the var is 64 // set to the CacheDir 65 dir2 := c.MkDir() 66 dirs.SetRootDir(dir2) 67 c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir) 68 69 // finally test that it's insufficient to just have the conf dir, we need 70 // specifically the cache dir 71 dir3 := c.MkDir() 72 err = os.MkdirAll(filepath.Join(dir3, "/etc/apparmor.d"), 0755) 73 c.Assert(err, IsNil) 74 dirs.SetRootDir(dir3) 75 c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir) 76 } 77 78 func (*apparmorSuite) TestMockAppArmorLevel(c *C) { 79 for _, lvl := range []apparmor.LevelType{apparmor.Unsupported, apparmor.Unusable, apparmor.Partial, apparmor.Full} { 80 restore := apparmor.MockLevel(lvl) 81 c.Check(apparmor.ProbedLevel(), Equals, lvl) 82 c.Check(apparmor.Summary(), testutil.Contains, "mocked apparmor level: ") 83 features, err := apparmor.KernelFeatures() 84 c.Check(err, IsNil) 85 c.Check(features, DeepEquals, []string{"mocked-kernel-feature"}) 86 features, err = apparmor.ParserFeatures() 87 c.Check(err, IsNil) 88 c.Check(features, DeepEquals, []string{"mocked-parser-feature"}) 89 restore() 90 } 91 } 92 93 // Using MockAppArmorFeatures yields in apparmor assessment 94 func (*apparmorSuite) TestMockAppArmorFeatures(c *C) { 95 // No apparmor in the kernel, apparmor is disabled. 96 restore := apparmor.MockFeatures([]string{}, os.ErrNotExist, []string{}, nil) 97 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported) 98 c.Check(apparmor.Summary(), Equals, "apparmor not enabled") 99 features, err := apparmor.KernelFeatures() 100 c.Assert(err, Equals, os.ErrNotExist) 101 c.Check(features, DeepEquals, []string{}) 102 features, err = apparmor.ParserFeatures() 103 c.Assert(err, IsNil) 104 c.Check(features, DeepEquals, []string{}) 105 restore() 106 107 // No apparmor_parser, apparmor is disabled. 108 restore = apparmor.MockFeatures([]string{}, nil, []string{}, os.ErrNotExist) 109 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported) 110 c.Check(apparmor.Summary(), Equals, "apparmor_parser not found") 111 features, err = apparmor.KernelFeatures() 112 c.Assert(err, IsNil) 113 c.Check(features, DeepEquals, []string{}) 114 features, err = apparmor.ParserFeatures() 115 c.Assert(err, Equals, os.ErrNotExist) 116 c.Check(features, DeepEquals, []string{}) 117 restore() 118 119 // Complete kernel features but apparmor is unusable because of missing required parser features. 120 restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, []string{}, nil) 121 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable) 122 c.Check(apparmor.Summary(), Equals, "apparmor_parser is available but required parser features are missing: unsafe") 123 features, err = apparmor.KernelFeatures() 124 c.Assert(err, IsNil) 125 c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures) 126 features, err = apparmor.ParserFeatures() 127 c.Assert(err, IsNil) 128 c.Check(features, DeepEquals, []string{}) 129 restore() 130 131 // Complete parser features but apparmor is unusable because of missing required kernel features. 132 // The dummy feature is there to pretend that apparmor in the kernel is not entirely disabled. 133 restore = apparmor.MockFeatures([]string{"dummy-feature"}, nil, apparmor.RequiredParserFeatures, nil) 134 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable) 135 c.Check(apparmor.Summary(), Equals, "apparmor is enabled but required kernel features are missing: file") 136 features, err = apparmor.KernelFeatures() 137 c.Assert(err, IsNil) 138 c.Check(features, DeepEquals, []string{"dummy-feature"}) 139 features, err = apparmor.ParserFeatures() 140 c.Assert(err, IsNil) 141 c.Check(features, DeepEquals, apparmor.RequiredParserFeatures) 142 restore() 143 144 // Required kernel and parser features available, some optional features are missing though. 145 restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, apparmor.RequiredParserFeatures, nil) 146 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Partial) 147 c.Check(apparmor.Summary(), Equals, "apparmor is enabled but some kernel features are missing: caps, dbus, domain, mount, namespaces, network, ptrace, signal") 148 features, err = apparmor.KernelFeatures() 149 c.Assert(err, IsNil) 150 c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures) 151 features, err = apparmor.ParserFeatures() 152 c.Assert(err, IsNil) 153 c.Check(features, DeepEquals, apparmor.RequiredParserFeatures) 154 restore() 155 156 // Preferred kernel and parser features available. 157 restore = apparmor.MockFeatures(apparmor.PreferredKernelFeatures, nil, apparmor.PreferredParserFeatures, nil) 158 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Full) 159 c.Check(apparmor.Summary(), Equals, "apparmor is enabled and all features are available") 160 features, err = apparmor.KernelFeatures() 161 c.Assert(err, IsNil) 162 c.Check(features, DeepEquals, apparmor.PreferredKernelFeatures) 163 features, err = apparmor.ParserFeatures() 164 c.Assert(err, IsNil) 165 c.Check(features, DeepEquals, apparmor.PreferredParserFeatures) 166 restore() 167 } 168 169 const featuresSysPath = "sys/kernel/security/apparmor/features" 170 171 func (s *apparmorSuite) TestProbeAppArmorKernelFeatures(c *C) { 172 d := c.MkDir() 173 174 // Pretend that apparmor kernel features directory doesn't exist. 175 restore := apparmor.MockFsRootPath(d) 176 defer restore() 177 features, err := apparmor.ProbeKernelFeatures() 178 c.Assert(os.IsNotExist(err), Equals, true) 179 c.Check(features, DeepEquals, []string{}) 180 181 // Pretend that apparmor kernel features directory exists but is empty. 182 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath), 0755), IsNil) 183 features, err = apparmor.ProbeKernelFeatures() 184 c.Assert(err, IsNil) 185 c.Check(features, DeepEquals, []string{}) 186 187 // Pretend that apparmor kernel features directory contains some entries. 188 c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "foo"), 0755), IsNil) 189 c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "bar"), 0755), IsNil) 190 features, err = apparmor.ProbeKernelFeatures() 191 c.Assert(err, IsNil) 192 c.Check(features, DeepEquals, []string{"bar", "foo"}) 193 } 194 195 func (s *apparmorSuite) TestProbeAppArmorParserFeatures(c *C) { 196 d := c.MkDir() 197 198 var testcases = []struct { 199 exit string 200 features []string 201 }{ 202 {"exit 1", []string{}}, 203 {"exit 0", []string{"unsafe"}}, 204 } 205 206 for _, t := range testcases { 207 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", fmt.Sprintf("cat > %s/stdin; %s", d, t.exit)) 208 defer mockParserCmd.Restore() 209 restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 210 defer restore() 211 212 features, err := apparmor.ProbeParserFeatures() 213 c.Assert(err, IsNil) 214 c.Check(features, DeepEquals, t.features) 215 c.Check(mockParserCmd.Calls(), DeepEquals, [][]string{{"apparmor_parser", "--preprocess"}}) 216 data, err := ioutil.ReadFile(filepath.Join(d, "stdin")) 217 c.Assert(err, IsNil) 218 c.Check(string(data), Equals, "profile snap-test {\n change_profile unsafe /**,\n}") 219 } 220 221 // Pretend that we just don't have apparmor_parser at all. 222 restore := apparmor.MockParserSearchPath(c.MkDir()) 223 defer restore() 224 features, err := apparmor.ProbeParserFeatures() 225 c.Check(err, Equals, os.ErrNotExist) 226 c.Check(features, DeepEquals, []string{}) 227 } 228 229 func (s *apparmorSuite) TestInterfaceSystemKey(c *C) { 230 apparmor.FreshAppArmorAssessment() 231 232 d := c.MkDir() 233 restore := apparmor.MockFsRootPath(d) 234 defer restore() 235 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil) 236 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil) 237 238 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 239 defer mockParserCmd.Restore() 240 restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 241 defer restore() 242 243 apparmor.ProbedLevel() 244 245 features, err := apparmor.KernelFeatures() 246 c.Assert(err, IsNil) 247 c.Check(features, DeepEquals, []string{"network", "policy"}) 248 features, err = apparmor.ParserFeatures() 249 c.Assert(err, IsNil) 250 c.Check(features, DeepEquals, []string{"unsafe"}) 251 } 252 253 func (s *apparmorSuite) TestAppArmorParserMtime(c *C) { 254 // Pretend that we have apparmor_parser. 255 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 256 defer mockParserCmd.Restore() 257 restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 258 defer restore() 259 mtime := apparmor.ParserMtime() 260 fi, err := os.Stat(filepath.Join(mockParserCmd.BinDir(), "apparmor_parser")) 261 c.Assert(err, IsNil) 262 c.Check(mtime, Equals, fi.ModTime().Unix()) 263 264 // Pretend that we don't have apparmor_parser. 265 restore = apparmor.MockParserSearchPath(c.MkDir()) 266 defer restore() 267 mtime = apparmor.ParserMtime() 268 c.Check(mtime, Equals, int64(0)) 269 } 270 271 func (s *apparmorSuite) TestFeaturesProbedOnce(c *C) { 272 apparmor.FreshAppArmorAssessment() 273 274 d := c.MkDir() 275 restore := apparmor.MockFsRootPath(d) 276 defer restore() 277 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil) 278 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil) 279 280 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 281 defer mockParserCmd.Restore() 282 restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 283 defer restore() 284 285 features, err := apparmor.KernelFeatures() 286 c.Assert(err, IsNil) 287 c.Check(features, DeepEquals, []string{"network", "policy"}) 288 features, err = apparmor.ParserFeatures() 289 c.Assert(err, IsNil) 290 c.Check(features, DeepEquals, []string{"unsafe"}) 291 292 // this makes probing fails but is not done again 293 err = os.RemoveAll(d) 294 c.Assert(err, IsNil) 295 296 _, err = apparmor.KernelFeatures() 297 c.Assert(err, IsNil) 298 299 // this makes probing fails but is not done again 300 err = os.RemoveAll(mockParserCmd.BinDir()) 301 c.Assert(err, IsNil) 302 303 _, err = apparmor.ParserFeatures() 304 c.Assert(err, IsNil) 305 }