github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap/cmd_routine_file_access_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 main_test 21 22 import ( 23 "fmt" 24 "net/http" 25 "net/url" 26 "os/user" 27 "path/filepath" 28 "strings" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/client" 33 snap "github.com/snapcore/snapd/cmd/snap" 34 ) 35 36 type SnapRoutineFileAccessSuite struct { 37 BaseSnapSuite 38 39 fakeHome string 40 } 41 42 var _ = Suite(&SnapRoutineFileAccessSuite{}) 43 44 func (s *SnapRoutineFileAccessSuite) SetUpTest(c *C) { 45 s.BaseSnapSuite.SetUpTest(c) 46 47 s.fakeHome = c.MkDir() 48 u, err := user.Current() 49 c.Assert(err, IsNil) 50 s.AddCleanup(snap.MockUserCurrent(func() (*user.User, error) { 51 return &user.User{Uid: u.Uid, HomeDir: s.fakeHome}, nil 52 })) 53 } 54 55 func (s *SnapRoutineFileAccessSuite) setUpClient(c *C, isClassic, hasHome, hasRemovableMedia bool) { 56 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 57 switch r.URL.Path { 58 case "/v2/snaps/hello": 59 c.Check(r.Method, Equals, "GET") 60 // snap hello at revision 100 61 response := mockInfoJSONNoLicense 62 if isClassic { 63 response = strings.Replace(response, `"confinement": "strict"`, `"confinement": "classic"`, 1) 64 } 65 fmt.Fprintln(w, response) 66 case "/v2/connections": 67 c.Check(r.Method, Equals, "GET") 68 c.Check(r.URL.Path, Equals, "/v2/connections") 69 c.Check(r.URL.Query(), DeepEquals, url.Values{ 70 "snap": []string{"hello"}, 71 }) 72 connections := []client.Connection{} 73 if hasHome { 74 connections = append(connections, client.Connection{ 75 Slot: client.SlotRef{ 76 Snap: "core", 77 Name: "home", 78 }, 79 Plug: client.PlugRef{ 80 Snap: "hello", 81 Name: "home", 82 }, 83 Interface: "home", 84 }) 85 } 86 if hasRemovableMedia { 87 connections = append(connections, client.Connection{ 88 Slot: client.SlotRef{ 89 Snap: "core", 90 Name: "removable-media", 91 }, 92 Plug: client.PlugRef{ 93 Snap: "hello", 94 Name: "removable-media", 95 }, 96 Interface: "removable-media", 97 }) 98 } 99 result := client.Connections{Established: connections} 100 EncodeResponseBody(c, w, map[string]interface{}{ 101 "type": "sync", 102 "result": result, 103 }) 104 default: 105 c.Fatalf("unexpected request: %v", r) 106 } 107 }) 108 } 109 110 func (s *SnapRoutineFileAccessSuite) checkAccess(c *C, path, access string) { 111 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "file-access", "hello", path}) 112 c.Assert(err, IsNil) 113 c.Check(s.Stdout(), Equals, access) 114 c.Check(s.Stderr(), Equals, "") 115 s.ResetStdStreams() 116 } 117 118 func (s *SnapRoutineFileAccessSuite) checkBasicAccess(c *C) { 119 // Check access to SNAP_DATA and SNAP_COMMON 120 s.checkAccess(c, "/var/snap", "hidden\n") 121 s.checkAccess(c, "/var/snap/other-snap", "hidden\n") 122 s.checkAccess(c, "/var/snap/hello", "read-only\n") 123 s.checkAccess(c, "/var/snap/hello/common", "read-write\n") 124 s.checkAccess(c, "/var/snap/hello/current", "read-write\n") 125 s.checkAccess(c, "/var/snap/hello/100", "read-write\n") 126 s.checkAccess(c, "/var/snap/hello/99", "read-only\n") 127 128 // Check access to SNAP_USER_DATA and SNAP_USER_COMMON 129 s.checkAccess(c, filepath.Join(s.fakeHome, "snap"), "hidden\n") 130 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/other-snap"), "hidden\n") 131 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello"), "read-only\n") 132 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/common"), "read-write\n") 133 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/current"), "read-write\n") 134 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/100"), "read-write\n") 135 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/99"), "read-only\n") 136 } 137 138 func (s *SnapRoutineFileAccessSuite) TestAccessDefault(c *C) { 139 s.setUpClient(c, false, false, false) 140 s.checkBasicAccess(c) 141 142 // No access to root 143 s.checkAccess(c, "/", "hidden\n") 144 s.checkAccess(c, "/usr/lib/libfoo.so", "hidden\n") 145 // No access to removable media 146 s.checkAccess(c, "/media/foo", "hidden\n") 147 // No access to home directory 148 s.checkAccess(c, s.fakeHome, "hidden\n") 149 s.checkAccess(c, filepath.Join(s.fakeHome, "Documents"), "hidden\n") 150 } 151 152 func (s *SnapRoutineFileAccessSuite) TestAccessClassicConfinement(c *C) { 153 s.setUpClient(c, true, false, false) 154 155 // Classic confinement snaps run in the host file system 156 // namespace, so have access to everything. 157 s.checkAccess(c, "/", "read-write\n") 158 s.checkAccess(c, "/usr/lib/libfoo.so", "read-write\n") 159 s.checkAccess(c, "/", "read-write\n") 160 s.checkAccess(c, s.fakeHome, "read-write\n") 161 s.checkAccess(c, filepath.Join(s.fakeHome, "snap/other-snap"), "read-write\n") 162 } 163 164 func (s *SnapRoutineFileAccessSuite) TestAccessHomeInterface(c *C) { 165 s.setUpClient(c, false, true, false) 166 s.checkBasicAccess(c) 167 168 // Access to non-hidden files in the home directory 169 s.checkAccess(c, s.fakeHome, "read-write\n") 170 s.checkAccess(c, filepath.Join(s.fakeHome, "Documents/foo.txt"), "read-write\n") 171 s.checkAccess(c, filepath.Join(s.fakeHome, "Documents/.hidden"), "read-write\n") 172 s.checkAccess(c, filepath.Join(s.fakeHome, ".config"), "hidden\n") 173 } 174 175 func (s *SnapRoutineFileAccessSuite) TestAccessRemovableMedia(c *C) { 176 s.setUpClient(c, false, false, true) 177 s.checkBasicAccess(c) 178 179 s.checkAccess(c, "/mnt", "read-write\n") 180 s.checkAccess(c, "/mnt/path/file.txt", "read-write\n") 181 s.checkAccess(c, "/media", "read-write\n") 182 s.checkAccess(c, "/media/path/file.txt", "read-write\n") 183 s.checkAccess(c, "/run/media", "read-write\n") 184 s.checkAccess(c, "/run/media/path/file.txt", "read-write\n") 185 }