github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  }