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  }