gitee.com/mysnapcore/mysnapd@v0.1.0/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  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"testing"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"gitee.com/mysnapcore/mysnapd/dirs"
    34  	"gitee.com/mysnapcore/mysnapd/osutil"
    35  	"gitee.com/mysnapcore/mysnapd/sandbox/apparmor"
    36  	"gitee.com/mysnapcore/mysnapd/testutil"
    37  )
    38  
    39  func TestApparmor(t *testing.T) {
    40  	TestingT(t)
    41  }
    42  
    43  type apparmorSuite struct {
    44  	testutil.BaseTest
    45  }
    46  
    47  var _ = Suite(&apparmorSuite{})
    48  
    49  func (s *apparmorSuite) SetUpTest(c *C) {
    50  	s.BaseTest.SetUpTest(c)
    51  	s.AddCleanup(func() {
    52  		configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd")
    53  		if err := os.Remove(configFile); err != nil {
    54  			c.Assert(os.IsNotExist(err), Equals, true)
    55  		}
    56  	})
    57  }
    58  
    59  func (*apparmorSuite) TestAppArmorLevelTypeStringer(c *C) {
    60  	c.Check(apparmor.Unknown.String(), Equals, "unknown")
    61  	c.Check(apparmor.Unsupported.String(), Equals, "none")
    62  	c.Check(apparmor.Unusable.String(), Equals, "unusable")
    63  	c.Check(apparmor.Partial.String(), Equals, "partial")
    64  	c.Check(apparmor.Full.String(), Equals, "full")
    65  	c.Check(apparmor.LevelType(42).String(), Equals, "AppArmorLevelType:42")
    66  }
    67  
    68  func (*apparmorSuite) TestAppArmorSystemCacheFallsback(c *C) {
    69  	// if we create the system cache dir under a new rootdir, then the
    70  	// SystemCacheDir should take that value
    71  	dir1 := c.MkDir()
    72  	systemCacheDir := filepath.Join(dir1, "/etc/apparmor.d/cache")
    73  	err := os.MkdirAll(systemCacheDir, 0755)
    74  	c.Assert(err, IsNil)
    75  	dirs.SetRootDir(dir1)
    76  	c.Assert(apparmor.SystemCacheDir, Equals, systemCacheDir)
    77  
    78  	// but if we set a new root dir without the system cache dir, now the var is
    79  	// set to the CacheDir
    80  	dir2 := c.MkDir()
    81  	dirs.SetRootDir(dir2)
    82  	c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir)
    83  
    84  	// finally test that it's insufficient to just have the conf dir, we need
    85  	// specifically the cache dir
    86  	dir3 := c.MkDir()
    87  	err = os.MkdirAll(filepath.Join(dir3, "/etc/apparmor.d"), 0755)
    88  	c.Assert(err, IsNil)
    89  	dirs.SetRootDir(dir3)
    90  	c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir)
    91  }
    92  
    93  func (*apparmorSuite) TestMockAppArmorLevel(c *C) {
    94  	for _, lvl := range []apparmor.LevelType{apparmor.Unsupported, apparmor.Unusable, apparmor.Partial, apparmor.Full} {
    95  		restore := apparmor.MockLevel(lvl)
    96  		c.Check(apparmor.ProbedLevel(), Equals, lvl)
    97  		c.Check(apparmor.Summary(), testutil.Contains, "mocked apparmor level: ")
    98  		features, err := apparmor.KernelFeatures()
    99  		c.Check(err, IsNil)
   100  		c.Check(features, DeepEquals, []string{"mocked-kernel-feature"})
   101  		features, err = apparmor.ParserFeatures()
   102  		c.Check(err, IsNil)
   103  		c.Check(features, DeepEquals, []string{"mocked-parser-feature"})
   104  		restore()
   105  	}
   106  }
   107  
   108  // Using MockAppArmorFeatures yields in apparmor assessment
   109  func (*apparmorSuite) TestMockAppArmorFeatures(c *C) {
   110  	// No apparmor in the kernel, apparmor is disabled.
   111  	restore := apparmor.MockFeatures([]string{}, os.ErrNotExist, []string{}, nil)
   112  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported)
   113  	c.Check(apparmor.Summary(), Equals, "apparmor not enabled")
   114  	features, err := apparmor.KernelFeatures()
   115  	c.Assert(err, Equals, os.ErrNotExist)
   116  	c.Check(features, DeepEquals, []string{})
   117  	features, err = apparmor.ParserFeatures()
   118  	c.Assert(err, IsNil)
   119  	c.Check(features, DeepEquals, []string{})
   120  	restore()
   121  
   122  	// No apparmor_parser, apparmor is disabled.
   123  	restore = apparmor.MockFeatures([]string{}, nil, []string{}, os.ErrNotExist)
   124  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported)
   125  	c.Check(apparmor.Summary(), Equals, "apparmor_parser not found")
   126  	features, err = apparmor.KernelFeatures()
   127  	c.Assert(err, IsNil)
   128  	c.Check(features, DeepEquals, []string{})
   129  	features, err = apparmor.ParserFeatures()
   130  	c.Assert(err, Equals, os.ErrNotExist)
   131  	c.Check(features, DeepEquals, []string{})
   132  	restore()
   133  
   134  	// Complete kernel features but apparmor is unusable because of missing required parser features.
   135  	restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, []string{}, nil)
   136  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable)
   137  	c.Check(apparmor.Summary(), Equals, "apparmor_parser is available but required parser features are missing: unsafe")
   138  	features, err = apparmor.KernelFeatures()
   139  	c.Assert(err, IsNil)
   140  	c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures)
   141  	features, err = apparmor.ParserFeatures()
   142  	c.Assert(err, IsNil)
   143  	c.Check(features, DeepEquals, []string{})
   144  	restore()
   145  
   146  	// Complete parser features but apparmor is unusable because of missing required kernel features.
   147  	// The test feature is there to pretend that apparmor in the kernel is not entirely disabled.
   148  	restore = apparmor.MockFeatures([]string{"test-feature"}, nil, apparmor.RequiredParserFeatures, nil)
   149  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable)
   150  	c.Check(apparmor.Summary(), Equals, "apparmor is enabled but required kernel features are missing: file")
   151  	features, err = apparmor.KernelFeatures()
   152  	c.Assert(err, IsNil)
   153  	c.Check(features, DeepEquals, []string{"test-feature"})
   154  	features, err = apparmor.ParserFeatures()
   155  	c.Assert(err, IsNil)
   156  	c.Check(features, DeepEquals, apparmor.RequiredParserFeatures)
   157  	restore()
   158  
   159  	// Required kernel and parser features available, some optional features are missing though.
   160  	restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, apparmor.RequiredParserFeatures, nil)
   161  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Partial)
   162  	c.Check(apparmor.Summary(), Equals, "apparmor is enabled but some kernel features are missing: caps, dbus, domain, mount, namespaces, network, ptrace, signal")
   163  	features, err = apparmor.KernelFeatures()
   164  	c.Assert(err, IsNil)
   165  	c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures)
   166  	features, err = apparmor.ParserFeatures()
   167  	c.Assert(err, IsNil)
   168  	c.Check(features, DeepEquals, apparmor.RequiredParserFeatures)
   169  	restore()
   170  
   171  	// Preferred kernel and parser features available.
   172  	restore = apparmor.MockFeatures(apparmor.PreferredKernelFeatures, nil, apparmor.PreferredParserFeatures, nil)
   173  	c.Check(apparmor.ProbedLevel(), Equals, apparmor.Full)
   174  	c.Check(apparmor.Summary(), Equals, "apparmor is enabled and all features are available")
   175  	features, err = apparmor.KernelFeatures()
   176  	c.Assert(err, IsNil)
   177  	c.Check(features, DeepEquals, apparmor.PreferredKernelFeatures)
   178  	features, err = apparmor.ParserFeatures()
   179  	c.Assert(err, IsNil)
   180  	c.Check(features, DeepEquals, apparmor.PreferredParserFeatures)
   181  	restore()
   182  }
   183  
   184  const featuresSysPath = "sys/kernel/security/apparmor/features"
   185  
   186  func (s *apparmorSuite) TestProbeAppArmorKernelFeatures(c *C) {
   187  	d := c.MkDir()
   188  
   189  	// Pretend that apparmor kernel features directory doesn't exist.
   190  	restore := apparmor.MockFsRootPath(d)
   191  	defer restore()
   192  	features, err := apparmor.ProbeKernelFeatures()
   193  	c.Assert(os.IsNotExist(err), Equals, true)
   194  	c.Check(features, DeepEquals, []string{})
   195  
   196  	// Pretend that apparmor kernel features directory exists but is empty.
   197  	c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath), 0755), IsNil)
   198  	features, err = apparmor.ProbeKernelFeatures()
   199  	c.Assert(err, IsNil)
   200  	c.Check(features, DeepEquals, []string{})
   201  
   202  	// Pretend that apparmor kernel features directory contains some entries.
   203  	c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "foo"), 0755), IsNil)
   204  	c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "bar"), 0755), IsNil)
   205  	features, err = apparmor.ProbeKernelFeatures()
   206  	c.Assert(err, IsNil)
   207  	c.Check(features, DeepEquals, []string{"bar", "foo"})
   208  }
   209  
   210  func (s *apparmorSuite) TestProbeAppArmorParserFeatures(c *C) {
   211  
   212  	var testcases = []struct {
   213  		exitCodes   []int
   214  		expFeatures []string
   215  	}{
   216  		{
   217  			exitCodes: []int{1, 1, 1, 1, 1},
   218  		},
   219  		{
   220  			exitCodes:   []int{1, 0, 1, 1, 1},
   221  			expFeatures: []string{"qipcrtr-socket"},
   222  		},
   223  		{
   224  			exitCodes:   []int{0, 1, 1, 1, 1},
   225  			expFeatures: []string{"unsafe"},
   226  		},
   227  		{
   228  			exitCodes:   []int{1, 1, 1, 0, 1},
   229  			expFeatures: []string{"cap-audit-read"},
   230  		},
   231  		{
   232  			exitCodes:   []int{0, 0, 1, 1, 1},
   233  			expFeatures: []string{"qipcrtr-socket", "unsafe"},
   234  		},
   235  		{
   236  			exitCodes:   []int{0, 0, 0, 0, 0},
   237  			expFeatures: []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"},
   238  		},
   239  	}
   240  
   241  	for _, t := range testcases {
   242  		d := c.MkDir()
   243  		contents := ""
   244  		for _, code := range t.exitCodes {
   245  			contents += fmt.Sprintf("%d ", code)
   246  		}
   247  		err := ioutil.WriteFile(filepath.Join(d, "codes"), []byte(contents), 0755)
   248  		c.Assert(err, IsNil)
   249  		mockParserCmd := testutil.MockCommand(c, "apparmor_parser", fmt.Sprintf(`
   250  cat >> %[1]s/stdin
   251  echo "" >> %[1]s/stdin
   252  
   253  read -r EXIT_CODE CODES_FOR_NEXT_CALLS < %[1]s/codes
   254  echo "$CODES_FOR_NEXT_CALLS" > %[1]s/codes
   255  
   256  exit "$EXIT_CODE"
   257  `, d))
   258  		defer mockParserCmd.Restore()
   259  		restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir())
   260  		defer restore()
   261  
   262  		features, err := apparmor.ProbeParserFeatures()
   263  		c.Assert(err, IsNil)
   264  		if len(t.expFeatures) == 0 {
   265  			c.Check(features, HasLen, 0)
   266  		} else {
   267  			c.Check(features, DeepEquals, t.expFeatures)
   268  		}
   269  
   270  		var expectedCalls [][]string
   271  		for range t.exitCodes {
   272  			expectedCalls = append(expectedCalls, []string{"apparmor_parser", "--preprocess"})
   273  		}
   274  		c.Check(mockParserCmd.Calls(), DeepEquals, expectedCalls)
   275  		data, err := ioutil.ReadFile(filepath.Join(d, "stdin"))
   276  		c.Assert(err, IsNil)
   277  		c.Check(string(data), Equals, `profile snap-test {
   278   change_profile unsafe /**,
   279  }
   280  profile snap-test {
   281   network qipcrtr dgram,
   282  }
   283  profile snap-test {
   284   capability bpf,
   285  }
   286  profile snap-test {
   287   capability audit_read,
   288  }
   289  profile snap-test {
   290   mqueue,
   291  }
   292  `)
   293  	}
   294  
   295  	// Pretend that we just don't have apparmor_parser at all.
   296  	restore := apparmor.MockParserSearchPath(c.MkDir())
   297  	defer restore()
   298  	features, err := apparmor.ProbeParserFeatures()
   299  	c.Check(err, Equals, os.ErrNotExist)
   300  	c.Check(features, DeepEquals, []string{})
   301  }
   302  
   303  func (s *apparmorSuite) TestInterfaceSystemKey(c *C) {
   304  	apparmor.FreshAppArmorAssessment()
   305  
   306  	d := c.MkDir()
   307  	restore := apparmor.MockFsRootPath(d)
   308  	defer restore()
   309  	c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil)
   310  	c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil)
   311  
   312  	mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "")
   313  	defer mockParserCmd.Restore()
   314  	restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir())
   315  	defer restore()
   316  
   317  	apparmor.ProbedLevel()
   318  
   319  	features, err := apparmor.KernelFeatures()
   320  	c.Assert(err, IsNil)
   321  	c.Check(features, DeepEquals, []string{"network", "policy"})
   322  	features, err = apparmor.ParserFeatures()
   323  	c.Assert(err, IsNil)
   324  	c.Check(features, DeepEquals, []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"})
   325  }
   326  
   327  func (s *apparmorSuite) TestAppArmorParserMtime(c *C) {
   328  	// Pretend that we have apparmor_parser.
   329  	mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "")
   330  	defer mockParserCmd.Restore()
   331  	restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir())
   332  	defer restore()
   333  	mtime := apparmor.ParserMtime()
   334  	fi, err := os.Stat(filepath.Join(mockParserCmd.BinDir(), "apparmor_parser"))
   335  	c.Assert(err, IsNil)
   336  	c.Check(mtime, Equals, fi.ModTime().Unix())
   337  
   338  	// Pretend that we don't have apparmor_parser.
   339  	restore = apparmor.MockParserSearchPath(c.MkDir())
   340  	defer restore()
   341  	mtime = apparmor.ParserMtime()
   342  	c.Check(mtime, Equals, int64(0))
   343  }
   344  
   345  func (s *apparmorSuite) TestFeaturesProbedOnce(c *C) {
   346  	apparmor.FreshAppArmorAssessment()
   347  
   348  	d := c.MkDir()
   349  	restore := apparmor.MockFsRootPath(d)
   350  	defer restore()
   351  	c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil)
   352  	c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil)
   353  
   354  	mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "")
   355  	defer mockParserCmd.Restore()
   356  	restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir())
   357  	defer restore()
   358  
   359  	features, err := apparmor.KernelFeatures()
   360  	c.Assert(err, IsNil)
   361  	c.Check(features, DeepEquals, []string{"network", "policy"})
   362  	features, err = apparmor.ParserFeatures()
   363  	c.Assert(err, IsNil)
   364  	c.Check(features, DeepEquals, []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"})
   365  
   366  	// this makes probing fails but is not done again
   367  	err = os.RemoveAll(d)
   368  	c.Assert(err, IsNil)
   369  
   370  	_, err = apparmor.KernelFeatures()
   371  	c.Assert(err, IsNil)
   372  
   373  	// this makes probing fails but is not done again
   374  	err = os.RemoveAll(mockParserCmd.BinDir())
   375  	c.Assert(err, IsNil)
   376  
   377  	_, err = apparmor.ParserFeatures()
   378  	c.Assert(err, IsNil)
   379  }
   380  
   381  func (s *apparmorSuite) TestValidateFreeFromAAREUnhappy(c *C) {
   382  	var testCases = []string{"a?", "*b", "c[c", "dd]", "e{", "f}", "g^", `h"`, "f\000", "g\x00"}
   383  
   384  	for _, s := range testCases {
   385  		c.Check(apparmor.ValidateNoAppArmorRegexp(s), ErrorMatches, ".* contains a reserved apparmor char from .*", Commentf("%q is not raising an error", s))
   386  	}
   387  }
   388  
   389  func (s *apparmorSuite) TestValidateFreeFromAAREhappy(c *C) {
   390  	var testCases = []string{"foo", "BaR", "b-z", "foo+bar", "b00m!", "be/ep", "a%b", "a&b", "a(b", "a)b", "a=b", "a#b", "a~b", "a'b", "a_b", "a,b", "a;b", "a>b", "a<b", "a|b"}
   391  
   392  	for _, s := range testCases {
   393  		c.Check(apparmor.ValidateNoAppArmorRegexp(s), IsNil, Commentf("%q raised an error but shouldn't", s))
   394  	}
   395  }
   396  
   397  func (s *apparmorSuite) TestUpdateHomedirsTunableMkdirFail(c *C) {
   398  	restore := apparmor.MockMkdirAll(func(string, os.FileMode) error {
   399  		return errors.New("mkdir failure")
   400  	})
   401  	defer restore()
   402  
   403  	err := apparmor.UpdateHomedirsTunable([]string{"does", "not", "matter"})
   404  	c.Check(err, ErrorMatches, `cannot create AppArmor tunable directory: mkdir failure`)
   405  }
   406  
   407  func (s *apparmorSuite) TestUpdateHomedirsTunableWriteFail(c *C) {
   408  	restore := apparmor.MockMkdirAll(func(string, os.FileMode) error {
   409  		return nil
   410  	})
   411  	defer restore()
   412  
   413  	restore = apparmor.MockAtomicWrite(func(string, io.Reader, os.FileMode, osutil.AtomicWriteFlags) error {
   414  		return errors.New("write failure")
   415  	})
   416  	defer restore()
   417  
   418  	err := apparmor.UpdateHomedirsTunable([]string{"does", "not", "matter"})
   419  	c.Check(err, ErrorMatches, `write failure`)
   420  }
   421  
   422  func (s *apparmorSuite) TestUpdateHomedirsTunableHappy(c *C) {
   423  	err := apparmor.UpdateHomedirsTunable([]string{"/home/a", "/dir2"})
   424  	c.Assert(err, IsNil)
   425  	configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd")
   426  	fileContents, err := ioutil.ReadFile(configFile)
   427  	c.Assert(err, IsNil)
   428  	c.Check(string(fileContents), Equals,
   429  		`# Generated by snapd -- DO NOT EDIT!`+"\n"+`@{HOMEDIRS}+="/home/a" "/dir2"`)
   430  }
   431  
   432  func (s *apparmorSuite) TestUpdateHomedirsTunableHappyNoDirs(c *C) {
   433  	err := apparmor.UpdateHomedirsTunable([]string{})
   434  	c.Check(err, IsNil)
   435  	configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd")
   436  	c.Check(osutil.FileExists(configFile), Equals, false)
   437  }