github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/daemon/access_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 daemon_test 21 22 import ( 23 "net/http" 24 "net/http/httptest" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/client" 29 "github.com/snapcore/snapd/daemon" 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/logger" 32 "github.com/snapcore/snapd/overlord/auth" 33 "github.com/snapcore/snapd/polkit" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 type accessSuite struct{} 38 39 var _ = Suite(&accessSuite{}) 40 41 var ( 42 errForbidden = daemon.Forbidden("access denied") 43 errUnauthorized = daemon.Unauthorized("access denied") 44 ) 45 46 func (s *accessSuite) TestOpenAccess(c *C) { 47 var ac daemon.AccessChecker = daemon.OpenAccess{} 48 49 // openAccess denies access from snapd-snap.socket 50 ucred := &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapSocket} 51 c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden) 52 53 // Access allowed from snapd.socket 54 ucred.Socket = dirs.SnapdSocket 55 c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil) 56 57 // Access forbidden without peer credentials. This will need 58 // to be revisited if the API is ever exposed over TCP. 59 c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errForbidden) 60 } 61 62 func (s *accessSuite) TestAuthenticatedAccess(c *C) { 63 restore := daemon.MockCheckPolkitAction(func(r *http.Request, ucred *daemon.Ucrednet, action string) *daemon.APIError { 64 // Polkit is not consulted if no action is specified 65 c.Fail() 66 return daemon.Forbidden("access denied") 67 }) 68 defer restore() 69 70 var ac daemon.AccessChecker = daemon.AuthenticatedAccess{} 71 72 req := httptest.NewRequest("GET", "/", nil) 73 user := &auth.UserState{} 74 75 // authenticatedAccess denies access from snapd-snap.socket 76 ucred := &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: dirs.SnapSocket} 77 c.Check(ac.CheckAccess(nil, req, ucred, nil), DeepEquals, errForbidden) 78 c.Check(ac.CheckAccess(nil, req, ucred, user), DeepEquals, errForbidden) 79 80 // the same for unknown sockets 81 ucred = &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: "unexpected.socket"} 82 c.Check(ac.CheckAccess(nil, req, ucred, nil), DeepEquals, errForbidden) 83 84 // With macaroon auth, a normal user is granted access 85 ucred = &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapdSocket} 86 c.Check(ac.CheckAccess(nil, req, ucred, user), IsNil) 87 88 // Macaroon access requires peer credentials 89 c.Check(ac.CheckAccess(nil, req, nil, user), DeepEquals, errForbidden) 90 91 // Without macaroon auth, normal users are unauthorized 92 c.Check(ac.CheckAccess(nil, req, ucred, nil), DeepEquals, errUnauthorized) 93 94 // The root user is granted access without a macaroon 95 ucred = &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: dirs.SnapdSocket} 96 c.Check(ac.CheckAccess(nil, req, ucred, nil), IsNil) 97 } 98 99 func (s *accessSuite) TestAuthenticatedAccessPolkit(c *C) { 100 var ac daemon.AccessChecker = daemon.AuthenticatedAccess{Polkit: "action-id"} 101 102 req := httptest.NewRequest("GET", "/", nil) 103 user := &auth.UserState{} 104 ucred := &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: dirs.SnapdSocket} 105 106 // polkit is not checked if any of: 107 // * ucred is missing 108 // * macaroon auth is provided 109 // * user is root 110 restore := daemon.MockCheckPolkitAction(func(r *http.Request, ucred *daemon.Ucrednet, action string) *daemon.APIError { 111 c.Fail() 112 return daemon.Forbidden("access denied") 113 }) 114 defer restore() 115 c.Check(ac.CheckAccess(nil, req, nil, nil), DeepEquals, errForbidden) 116 c.Check(ac.CheckAccess(nil, req, nil, user), DeepEquals, errForbidden) 117 c.Check(ac.CheckAccess(nil, req, ucred, nil), IsNil) 118 119 // polkit is checked for regular users without macaroon auth 120 restore = daemon.MockCheckPolkitAction(func(r *http.Request, u *daemon.Ucrednet, action string) *daemon.APIError { 121 c.Check(r, Equals, req) 122 c.Check(u, Equals, ucred) 123 c.Check(action, Equals, "action-id") 124 return nil 125 }) 126 defer restore() 127 ucred = &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapdSocket} 128 c.Check(ac.CheckAccess(nil, req, ucred, nil), IsNil) 129 } 130 131 func (s *accessSuite) TestCheckPolkitActionImpl(c *C) { 132 logbuf, restore := logger.MockLogger() 133 defer restore() 134 135 req := httptest.NewRequest("GET", "/", nil) 136 ucred := &daemon.Ucrednet{Uid: 42, Pid: 1000, Socket: dirs.SnapdSocket} 137 138 // Access granted if polkit authorizes the request 139 restore = daemon.MockPolkitCheckAuthorization(func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 140 c.Check(pid, Equals, int32(1000)) 141 c.Check(uid, Equals, uint32(42)) 142 c.Check(actionId, Equals, "action-id") 143 c.Check(details, IsNil) 144 c.Check(flags, Equals, polkit.CheckFlags(0)) 145 return true, nil 146 }) 147 defer restore() 148 c.Check(daemon.CheckPolkitActionImpl(req, ucred, "action-id"), IsNil) 149 c.Check(logbuf.String(), Equals, "") 150 151 // Unauthorized if polkit denies the request 152 restore = daemon.MockPolkitCheckAuthorization(func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 153 return false, nil 154 }) 155 defer restore() 156 c.Check(daemon.CheckPolkitActionImpl(req, ucred, "action-id"), DeepEquals, errUnauthorized) 157 c.Check(logbuf.String(), Equals, "") 158 159 // Cancelled if the user dismisses the auth check 160 restore = daemon.MockPolkitCheckAuthorization(func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 161 return false, polkit.ErrDismissed 162 }) 163 defer restore() 164 rspe := daemon.CheckPolkitActionImpl(req, ucred, "action-id") 165 c.Check(rspe, DeepEquals, daemon.AuthCancelled("cancelled")) 166 c.Check(logbuf.String(), Equals, "") 167 168 // The X-Allow-Interaction header can be set to tell polkitd 169 // that interaction with the user is allowed. 170 req.Header.Set(client.AllowInteractionHeader, "true") 171 restore = daemon.MockPolkitCheckAuthorization(func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 172 c.Check(flags, Equals, polkit.CheckFlags(polkit.CheckAllowInteraction)) 173 return true, nil 174 }) 175 defer restore() 176 c.Check(daemon.CheckPolkitActionImpl(req, ucred, "action-id"), IsNil) 177 c.Check(logbuf.String(), Equals, "") 178 179 // Bad values in the request header are logged 180 req.Header.Set(client.AllowInteractionHeader, "garbage") 181 restore = daemon.MockPolkitCheckAuthorization(func(pid int32, uid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { 182 c.Check(flags, Equals, polkit.CheckFlags(0)) 183 return true, nil 184 }) 185 defer restore() 186 c.Check(daemon.CheckPolkitActionImpl(req, ucred, "action-id"), IsNil) 187 c.Check(logbuf.String(), testutil.Contains, "error parsing X-Allow-Interaction header:") 188 } 189 190 func (s *accessSuite) TestRootAccess(c *C) { 191 var ac daemon.AccessChecker = daemon.RootAccess{} 192 193 user := &auth.UserState{} 194 195 // rootAccess denies access without ucred 196 c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errForbidden) 197 c.Check(ac.CheckAccess(nil, nil, nil, user), DeepEquals, errForbidden) 198 199 // rootAccess denies access from snapd-snap.socket 200 ucred := &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: dirs.SnapSocket} 201 c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden) 202 c.Check(ac.CheckAccess(nil, nil, ucred, user), DeepEquals, errForbidden) 203 204 // Non-root users are forbidden, even with macaroon auth 205 ucred = &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapdSocket} 206 c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden) 207 c.Check(ac.CheckAccess(nil, nil, ucred, user), DeepEquals, errForbidden) 208 209 // Root is granted access 210 ucred = &daemon.Ucrednet{Uid: 0, Pid: 100, Socket: dirs.SnapdSocket} 211 c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil) 212 } 213 214 func (s *accessSuite) TestSnapAccess(c *C) { 215 var ac daemon.AccessChecker = daemon.SnapAccess{} 216 217 // snapAccess allows access from snapd-snap.socket 218 ucred := &daemon.Ucrednet{Uid: 42, Pid: 100, Socket: dirs.SnapSocket} 219 c.Check(ac.CheckAccess(nil, nil, ucred, nil), IsNil) 220 221 // access is forbidden on the main socket or without peer creds 222 ucred.Socket = dirs.SnapdSocket 223 c.Check(ac.CheckAccess(nil, nil, ucred, nil), DeepEquals, errForbidden) 224 c.Check(ac.CheckAccess(nil, nil, nil, nil), DeepEquals, errForbidden) 225 }