github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/usersession/agent/session_agent_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 agent_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"net"
    26  	"net/http"
    27  	"os"
    28  	"syscall"
    29  	"testing"
    30  	"time"
    31  
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/osutil/sys"
    37  	"github.com/snapcore/snapd/testutil"
    38  	"github.com/snapcore/snapd/usersession/agent"
    39  )
    40  
    41  func Test(t *testing.T) { TestingT(t) }
    42  
    43  type sessionAgentSuite struct {
    44  	testutil.DBusTest
    45  	socketPath string
    46  	client     *http.Client
    47  }
    48  
    49  var _ = Suite(&sessionAgentSuite{})
    50  
    51  func (s *sessionAgentSuite) SetUpTest(c *C) {
    52  	s.DBusTest.SetUpTest(c)
    53  	dirs.SetRootDir(c.MkDir())
    54  	xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid())
    55  	c.Assert(os.MkdirAll(xdgRuntimeDir, 0700), IsNil)
    56  	s.socketPath = fmt.Sprintf("%s/snapd-session-agent.socket", xdgRuntimeDir)
    57  
    58  	transport := &http.Transport{
    59  		Dial: func(_, _ string) (net.Conn, error) {
    60  			return net.Dial("unix", s.socketPath)
    61  		},
    62  		DisableKeepAlives: true,
    63  	}
    64  	s.client = &http.Client{Transport: transport}
    65  }
    66  
    67  func (s *sessionAgentSuite) TearDownTest(c *C) {
    68  	dirs.SetRootDir("")
    69  	logger.SetLogger(logger.NullLogger)
    70  	s.DBusTest.TearDownTest(c)
    71  }
    72  
    73  func (s *sessionAgentSuite) TestStartStop(c *C) {
    74  	agent, err := agent.New()
    75  	c.Assert(err, IsNil)
    76  	agent.Version = "42"
    77  	agent.Start()
    78  	defer func() { c.Check(agent.Stop(), IsNil) }()
    79  
    80  	// The agent has connected to the session bus
    81  	var hasOwner bool
    82  	c.Check(s.DBusTest.SessionBus.BusObject().Call("org.freedesktop.DBus.NameHasOwner", 0, "io.snapcraft.SessionAgent").Store(&hasOwner), IsNil)
    83  	c.Check(hasOwner, Equals, true)
    84  
    85  	// The agent is listening for REST API requests
    86  	response, err := s.client.Get("http://localhost/v1/session-info")
    87  	c.Assert(err, IsNil)
    88  	defer response.Body.Close()
    89  	c.Check(response.StatusCode, Equals, 200)
    90  
    91  	var rst struct {
    92  		Result struct {
    93  			Version string `json:"version"`
    94  		} `json:"result"`
    95  	}
    96  	c.Assert(json.NewDecoder(response.Body).Decode(&rst), IsNil)
    97  	c.Check(rst.Result.Version, Equals, "42")
    98  	response.Body.Close()
    99  
   100  	c.Check(agent.Stop(), IsNil)
   101  }
   102  
   103  func (s *sessionAgentSuite) TestDying(c *C) {
   104  	agent, err := agent.New()
   105  	c.Assert(err, IsNil)
   106  	agent.Start()
   107  	select {
   108  	case <-agent.Dying():
   109  		c.Error("agent.Dying() channel closed prematurely")
   110  	default:
   111  	}
   112  	go func() {
   113  		time.Sleep(5 * time.Millisecond)
   114  		c.Check(agent.Stop(), IsNil)
   115  	}()
   116  	select {
   117  	case <-agent.Dying():
   118  	case <-time.After(2 * time.Second):
   119  		c.Error("agent.Dying() channel was not closed when agent stopped")
   120  	}
   121  }
   122  
   123  func (s *sessionAgentSuite) TestExitOnIdle(c *C) {
   124  	agent, err := agent.New()
   125  	c.Assert(err, IsNil)
   126  	agent.IdleTimeout = 150 * time.Millisecond
   127  	startTime := time.Now()
   128  	agent.Start()
   129  	defer agent.Stop()
   130  
   131  	makeRequest := func() {
   132  		response, err := s.client.Get("http://localhost/v1/session-info")
   133  		c.Assert(err, IsNil)
   134  		defer response.Body.Close()
   135  		c.Check(response.StatusCode, Equals, 200)
   136  	}
   137  	makeRequest()
   138  	time.Sleep(25 * time.Millisecond)
   139  	makeRequest()
   140  
   141  	select {
   142  	case <-agent.Dying():
   143  	case <-time.After(2 * time.Second):
   144  		c.Fatal("agent did not exit after idle timeout expired")
   145  	}
   146  	elapsed := time.Since(startTime)
   147  	if elapsed < 175*time.Millisecond || elapsed > 450*time.Millisecond {
   148  		// The idle timeout should have been extended when we
   149  		// issued a second request after 25ms.
   150  		c.Errorf("Expected ellaped time close to 175 ms, but got %v", elapsed)
   151  	}
   152  }
   153  
   154  func (s *sessionAgentSuite) TestConnectFromOtherUser(c *C) {
   155  	logbuf, restore := logger.MockLogger()
   156  	defer restore()
   157  
   158  	// Mock connections to appear to come from a different user ID
   159  	uid := uint32(sys.Geteuid())
   160  	restore = agent.MockUcred(&syscall.Ucred{Uid: uid + 1}, nil)
   161  	defer restore()
   162  
   163  	sa, err := agent.New()
   164  	c.Assert(err, IsNil)
   165  	sa.Start()
   166  	defer sa.Stop()
   167  
   168  	_, err = s.client.Get("http://localhost/v1/session-info")
   169  	// This could be an EOF error or a failed read, depending on timing
   170  	c.Assert(err, ErrorMatches, "Get \"?http://localhost/v1/session-info\"?: .*")
   171  	logger.WithLoggerLock(func() {
   172  		c.Check(logbuf.String(), testutil.Contains, "Blocking request from user ID")
   173  	})
   174  }
   175  
   176  func (s *sessionAgentSuite) TestConnectFromRoot(c *C) {
   177  	logbuf, restore := logger.MockLogger()
   178  	defer restore()
   179  
   180  	// Mock connections to appear to come from root
   181  	restore = agent.MockUcred(&syscall.Ucred{Uid: 0}, nil)
   182  	defer restore()
   183  
   184  	sa, err := agent.New()
   185  	c.Assert(err, IsNil)
   186  	sa.Start()
   187  	defer sa.Stop()
   188  
   189  	response, err := s.client.Get("http://localhost/v1/session-info")
   190  	c.Assert(err, IsNil)
   191  	defer response.Body.Close()
   192  	c.Check(response.StatusCode, Equals, 200)
   193  	logger.WithLoggerLock(func() {
   194  		c.Check(logbuf.String(), Equals, "")
   195  	})
   196  }
   197  
   198  func (s *sessionAgentSuite) TestConnectWithFailedPeerCredentials(c *C) {
   199  	logbuf, restore := logger.MockLogger()
   200  	defer restore()
   201  
   202  	// Connections are dropped if peer credential lookup fails.
   203  	restore = agent.MockUcred(nil, fmt.Errorf("SO_PEERCRED failed"))
   204  	defer restore()
   205  
   206  	sa, err := agent.New()
   207  	c.Assert(err, IsNil)
   208  	sa.Start()
   209  	defer sa.Stop()
   210  
   211  	_, err = s.client.Get("http://localhost/v1/session-info")
   212  	c.Assert(err, ErrorMatches, "Get \"?http://localhost/v1/session-info\"?: .*")
   213  	logger.WithLoggerLock(func() {
   214  		c.Check(logbuf.String(), testutil.Contains, "Failed to retrieve peer credentials: SO_PEERCRED failed")
   215  	})
   216  }