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