github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/system_key_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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 interfaces_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"reflect"
    30  	"strings"
    31  
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/interfaces"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/sandbox/apparmor"
    38  	"github.com/snapcore/snapd/sandbox/cgroup"
    39  	"github.com/snapcore/snapd/sandbox/seccomp"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type systemKeySuite struct {
    44  	testutil.BaseTest
    45  
    46  	tmp                    string
    47  	apparmorFeatures       string
    48  	buildID                string
    49  	seccompCompilerVersion seccomp.VersionInfo
    50  }
    51  
    52  var _ = Suite(&systemKeySuite{})
    53  
    54  func (s *systemKeySuite) SetUpTest(c *C) {
    55  	s.BaseTest.SetUpTest(c)
    56  
    57  	s.tmp = c.MkDir()
    58  	dirs.SetRootDir(s.tmp)
    59  	err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755)
    60  	c.Assert(err, IsNil)
    61  	err = os.MkdirAll(dirs.DistroLibExecDir, 0755)
    62  	c.Assert(err, IsNil)
    63  	err = os.Symlink("/proc/self/exe", filepath.Join(dirs.DistroLibExecDir, "snapd"))
    64  	c.Assert(err, IsNil)
    65  
    66  	s.apparmorFeatures = filepath.Join(s.tmp, "/sys/kernel/security/apparmor/features")
    67  	s.buildID = "this-is-my-build-id"
    68  
    69  	s.seccompCompilerVersion = seccomp.VersionInfo("123 2.3.3 abcdef123 -")
    70  	testutil.MockCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), fmt.Sprintf(`
    71  if [ "$1" = "version-info" ]; then echo "%s"; exit 0; fi
    72  exit 1
    73  `, s.seccompCompilerVersion))
    74  
    75  	s.AddCleanup(seccomp.MockActions([]string{"allow", "errno", "kill", "log", "trace", "trap"}))
    76  }
    77  
    78  func (s *systemKeySuite) TearDownTest(c *C) {
    79  	s.BaseTest.TearDownTest(c)
    80  
    81  	dirs.SetRootDir("/")
    82  }
    83  
    84  func (s *systemKeySuite) testInterfaceWriteSystemKey(c *C, nfsHome, overlayRoot bool) {
    85  	var overlay string
    86  	if overlayRoot {
    87  		overlay = "overlay"
    88  	}
    89  	restore := interfaces.MockIsHomeUsingNFS(func() (bool, error) { return nfsHome, nil })
    90  	defer restore()
    91  
    92  	restore = interfaces.MockReadBuildID(func(p string) (string, error) {
    93  		c.Assert(p, Equals, filepath.Join(dirs.DistroLibExecDir, "snapd"))
    94  		return s.buildID, nil
    95  	})
    96  	defer restore()
    97  
    98  	restore = interfaces.MockIsRootWritableOverlay(func() (string, error) { return overlay, nil })
    99  	defer restore()
   100  
   101  	restore = cgroup.MockVersion(1, nil)
   102  	defer restore()
   103  
   104  	err := interfaces.WriteSystemKey()
   105  	c.Assert(err, IsNil)
   106  
   107  	systemKey, err := ioutil.ReadFile(dirs.SnapSystemKeyFile)
   108  	c.Assert(err, IsNil)
   109  
   110  	kernelFeatures, _ := apparmor.KernelFeatures()
   111  
   112  	apparmorFeaturesStr, err := json.Marshal(kernelFeatures)
   113  	c.Assert(err, IsNil)
   114  
   115  	apparmorParserMtime, err := json.Marshal(apparmor.ParserMtime())
   116  	c.Assert(err, IsNil)
   117  
   118  	parserFeatures, _ := apparmor.ParserFeatures()
   119  	apparmorParserFeaturesStr, err := json.Marshal(parserFeatures)
   120  	c.Assert(err, IsNil)
   121  
   122  	seccompActionsStr, err := json.Marshal(seccomp.Actions())
   123  	c.Assert(err, IsNil)
   124  
   125  	compiler, err := seccomp.NewCompiler(func(name string) (string, error) {
   126  		return filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), nil
   127  	})
   128  	c.Assert(err, IsNil)
   129  	seccompCompilerVersion, err := compiler.VersionInfo()
   130  	c.Assert(err, IsNil)
   131  	c.Assert(seccompCompilerVersion, Equals, s.seccompCompilerVersion)
   132  
   133  	c.Check(string(systemKey), testutil.EqualsWrapped, fmt.Sprintf(`{"version":%d,"build-id":"%s","apparmor-features":%s,"apparmor-parser-mtime":%s,"apparmor-parser-features":%s,"nfs-home":%v,"overlay-root":%q,"seccomp-features":%s,"seccomp-compiler-version":"%s","cgroup-version":"1"}`,
   134  		interfaces.SystemKeyVersion,
   135  		s.buildID,
   136  		apparmorFeaturesStr,
   137  		apparmorParserMtime,
   138  		apparmorParserFeaturesStr,
   139  		nfsHome,
   140  		overlay,
   141  		seccompActionsStr,
   142  		seccompCompilerVersion,
   143  	))
   144  }
   145  
   146  func (s *systemKeySuite) TestInterfaceWriteSystemKeyNoNFS(c *C) {
   147  	s.testInterfaceWriteSystemKey(c, false, false)
   148  }
   149  
   150  func (s *systemKeySuite) TestInterfaceWriteSystemKeyWithNFS(c *C) {
   151  	s.testInterfaceWriteSystemKey(c, true, false)
   152  }
   153  
   154  func (s *systemKeySuite) TestInterfaceWriteSystemKeyWithOverlayRoot(c *C) {
   155  	s.testInterfaceWriteSystemKey(c, false, true)
   156  }
   157  
   158  // bonus points to someone who actually runs this
   159  func (s *systemKeySuite) TestInterfaceWriteSystemKeyWithNFSWithOverlayRoot(c *C) {
   160  	s.testInterfaceWriteSystemKey(c, true, true)
   161  }
   162  
   163  func (s *systemKeySuite) TestInterfaceWriteSystemKeyErrorOnBuildID(c *C) {
   164  	restore := interfaces.MockIsHomeUsingNFS(func() (bool, error) { return false, nil })
   165  	defer restore()
   166  
   167  	restore = interfaces.MockReadBuildID(func(p string) (string, error) {
   168  		c.Assert(p, Equals, filepath.Join(dirs.DistroLibExecDir, "snapd"))
   169  		return "", fmt.Errorf("no build ID for you")
   170  	})
   171  	defer restore()
   172  
   173  	err := interfaces.WriteSystemKey()
   174  	c.Assert(err, ErrorMatches, "no build ID for you")
   175  }
   176  
   177  func (s *systemKeySuite) TestInterfaceSystemKeyMismatchHappy(c *C) {
   178  	s.AddCleanup(interfaces.MockSystemKey(`
   179  {
   180  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
   181  "apparmor-features": ["caps", "dbus"]
   182  }
   183  `))
   184  
   185  	// no system-key yet -> Error
   186  	c.Assert(osutil.FileExists(dirs.SnapSystemKeyFile), Equals, false)
   187  	_, err := interfaces.SystemKeyMismatch()
   188  	c.Assert(err, Equals, interfaces.ErrSystemKeyMissing)
   189  
   190  	// create a system-key -> no mismatch anymore
   191  	err = interfaces.WriteSystemKey()
   192  	c.Assert(err, IsNil)
   193  	mismatch, err := interfaces.SystemKeyMismatch()
   194  	c.Assert(err, IsNil)
   195  	c.Check(mismatch, Equals, false)
   196  
   197  	// change our system-key to have more apparmor features
   198  	s.AddCleanup(interfaces.MockSystemKey(`
   199  {
   200  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
   201  "apparmor-features": ["caps", "dbus", "more", "and", "more"]
   202  }
   203  `))
   204  	mismatch, err = interfaces.SystemKeyMismatch()
   205  	c.Assert(err, IsNil)
   206  	c.Check(mismatch, Equals, true)
   207  }
   208  
   209  func (s *systemKeySuite) TestInterfaceSystemKeyMismatchParserMtimeHappy(c *C) {
   210  	s.AddCleanup(interfaces.MockSystemKey(`
   211  {
   212  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
   213  "apparmor-parser-mtime": 1234
   214  }
   215  `))
   216  
   217  	// no system-key yet -> Error
   218  	c.Assert(osutil.FileExists(dirs.SnapSystemKeyFile), Equals, false)
   219  	_, err := interfaces.SystemKeyMismatch()
   220  	c.Assert(err, Equals, interfaces.ErrSystemKeyMissing)
   221  
   222  	// create a system-key -> no mismatch anymore
   223  	err = interfaces.WriteSystemKey()
   224  	c.Assert(err, IsNil)
   225  	mismatch, err := interfaces.SystemKeyMismatch()
   226  	c.Assert(err, IsNil)
   227  	c.Check(mismatch, Equals, false)
   228  
   229  	// change our system-key to have a different parser mtime
   230  	s.AddCleanup(interfaces.MockSystemKey(`
   231  {
   232  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
   233  "apparmor-parser-mtime": 5678
   234  }
   235  `))
   236  	mismatch, err = interfaces.SystemKeyMismatch()
   237  	c.Assert(err, IsNil)
   238  	c.Check(mismatch, Equals, true)
   239  }
   240  
   241  func (s *systemKeySuite) TestInterfaceSystemKeyMismatchVersions(c *C) {
   242  	// we calculcate v1
   243  	s.AddCleanup(interfaces.MockSystemKey(`
   244  {
   245  "version":1,
   246  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0"
   247  }`))
   248  	// and the on-disk version is v2
   249  	err := ioutil.WriteFile(dirs.SnapSystemKeyFile, []byte(`
   250  {
   251  "version":2,
   252  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0"
   253  }`), 0644)
   254  	c.Assert(err, IsNil)
   255  
   256  	// when we encounter different versions we get the right error
   257  	_, err = interfaces.SystemKeyMismatch()
   258  	c.Assert(err, Equals, interfaces.ErrSystemKeyVersion)
   259  }
   260  
   261  func (s *systemKeySuite) TestStaticVersion(c *C) {
   262  	// this is a static check to ensure we remember to bump the
   263  	// version when we add fields
   264  	//
   265  	// *** IF THIS FAILS, YOU NEED TO BUMP THE VERSION BEFORE "FIXING" THIS ***
   266  	var sk interfaces.SystemKey
   267  
   268  	// XXX: this checks needs to become smarter once we remove or change
   269  	// existing fields, in which case the version will gets a bump but the
   270  	// number of fields decreases or remains unchanged
   271  	c.Check(reflect.ValueOf(sk).NumField(), Equals, interfaces.SystemKeyVersion)
   272  
   273  	c.Check(fmt.Sprintf("%+v", sk), Equals, "{"+strings.Join([]string{
   274  		"Version:0",
   275  		"BuildID:",
   276  		"AppArmorFeatures:[]",
   277  		"AppArmorParserMtime:0",
   278  		"AppArmorParserFeatures:[]",
   279  		"NFSHome:false",
   280  		"OverlayRoot:",
   281  		"SecCompActions:[]",
   282  		"SeccompCompilerVersion:",
   283  		"CgroupVersion:",
   284  	}, " ")+"}")
   285  }
   286  
   287  func (s *systemKeySuite) TestRecordedSystemKey(c *C) {
   288  	_, err := interfaces.RecordedSystemKey()
   289  	c.Check(err, Equals, interfaces.ErrSystemKeyMissing)
   290  
   291  	restore := interfaces.MockSystemKey(`
   292  {
   293  "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
   294  "apparmor-features": ["caps"]
   295  }
   296  `)
   297  	defer restore()
   298  
   299  	c.Assert(interfaces.WriteSystemKey(), IsNil)
   300  
   301  	// just to ensure we really re-read it from the disk with RecordedSystemKey
   302  	interfaces.MockSystemKey(`{"build-id":"foo"}`)
   303  
   304  	key, err := interfaces.RecordedSystemKey()
   305  	c.Assert(err, IsNil)
   306  
   307  	sysKey, ok := key.(*interfaces.SystemKey)
   308  	c.Assert(ok, Equals, true)
   309  	c.Check(sysKey.BuildID, Equals, "7a94e9736c091b3984bd63f5aebfc883c4d859e0")
   310  }
   311  
   312  func (s *systemKeySuite) TestCurrentSystemKey(c *C) {
   313  	restore := interfaces.MockSystemKey(`{"build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0"}`)
   314  	defer restore()
   315  
   316  	key, err := interfaces.CurrentSystemKey()
   317  	c.Assert(err, IsNil)
   318  	sysKey, ok := key.(*interfaces.SystemKey)
   319  	c.Assert(ok, Equals, true)
   320  	c.Check(sysKey.BuildID, Equals, "7a94e9736c091b3984bd63f5aebfc883c4d859e0")
   321  }
   322  
   323  func (s *systemKeySuite) TestSystemKeysMatch(c *C) {
   324  	_, err := interfaces.SystemKeysMatch(nil, nil)
   325  	c.Check(err, ErrorMatches, `SystemKeysMatch: arguments are not system keys`)
   326  
   327  	restore := interfaces.MockSystemKey(`{"build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0"}`)
   328  	defer restore()
   329  
   330  	key1, err := interfaces.CurrentSystemKey()
   331  	c.Assert(err, IsNil)
   332  
   333  	_, err = interfaces.SystemKeysMatch(key1, nil)
   334  	c.Check(err, ErrorMatches, `SystemKeysMatch: arguments are not system keys`)
   335  
   336  	_, err = interfaces.SystemKeysMatch(nil, key1)
   337  	c.Check(err, ErrorMatches, `SystemKeysMatch: arguments are not system keys`)
   338  
   339  	interfaces.MockSystemKey(`{"build-id": "8888e9736c091b3984bd63f5aebfc883c4d85988"}`)
   340  	key2, err := interfaces.CurrentSystemKey()
   341  	c.Assert(err, IsNil)
   342  
   343  	ok, err := interfaces.SystemKeysMatch(key1, key2)
   344  	c.Assert(err, IsNil)
   345  	c.Check(ok, Equals, false)
   346  
   347  	key3, err := interfaces.CurrentSystemKey()
   348  	c.Assert(err, IsNil)
   349  
   350  	ok, err = interfaces.SystemKeysMatch(key2, key3)
   351  	c.Assert(err, IsNil)
   352  	c.Check(ok, Equals, true)
   353  }
   354  
   355  func (s *systemKeySuite) TestSystemKeysUnmarshalSame(c *C) {
   356  	// whitespace here simulates the serialization across HTTP, etc. that should
   357  	// not trigger any differences
   358  	// use a full system-key to fully test serialization, etc.
   359  	systemKeyJSON := `
   360  	{
   361  		"apparmor-features": [
   362  			"caps",
   363  			"dbus",
   364  			"domain",
   365  			"file",
   366  			"mount",
   367  			"namespaces",
   368  			"network",
   369  			"network_v8",
   370  			"policy",
   371  			"ptrace",
   372  			"query",
   373  			"rlimit",
   374  			"signal"
   375  		],
   376  		"apparmor-parser-features": [],
   377  		"apparmor-parser-mtime": 1589907589,
   378  		"build-id": "cb94e5eeee4cf7ecda53f8308a984cb155b55732",
   379  		"cgroup-version": "1",
   380  		"nfs-home": false,
   381  		"overlay-root": "",
   382  		"seccomp-compiler-version": "e6e309ad8aee052e5aa695dfaa040328ae1559c5 2.4.3 9b218ef9a4e508dd8a7f848095cb8875d10a4bf28428ad81fdc3f8dac89108f7 bpf-actlog",
   383  		"seccomp-features": [
   384  			"allow",
   385  			"errno",
   386  			"kill_process",
   387  			"kill_thread",
   388  			"log",
   389  			"trace",
   390  			"trap",
   391  			"user_notif"
   392  		],
   393  		"version": 10
   394  	}`
   395  
   396  	// write the mocked system key to disk
   397  	restore := interfaces.MockSystemKey(systemKeyJSON)
   398  	defer restore()
   399  	err := interfaces.WriteSystemKey()
   400  	c.Assert(err, IsNil)
   401  
   402  	// now unmarshal the specific json to a system key object
   403  	key1, err := interfaces.UnmarshalJSONSystemKey(bytes.NewBuffer([]byte(systemKeyJSON)))
   404  	c.Assert(err, IsNil)
   405  
   406  	// now read the system key from disk
   407  	key2, err := interfaces.RecordedSystemKey()
   408  	c.Assert(err, IsNil)
   409  
   410  	// the two system-keys should be the same
   411  	ok, err := interfaces.SystemKeysMatch(key1, key2)
   412  	c.Assert(err, IsNil)
   413  	c.Check(ok, Equals, true, Commentf("key1:%#v key2:%#v", key1, key2))
   414  }