github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_sideload_n_try.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-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 daemon
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"mime/multipart"
    27  	"os"
    28  	"path/filepath"
    29  	"strconv"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/asserts/snapasserts"
    33  	"github.com/snapcore/snapd/client"
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/i18n"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/overlord/assertstate"
    38  	"github.com/snapcore/snapd/overlord/auth"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/snapfile"
    43  )
    44  
    45  const maxReadBuflen = 1024 * 1024
    46  
    47  func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string, user *auth.UserState) Response {
    48  	route := c.d.router.Get(stateChangeCmd.Path)
    49  	if route == nil {
    50  		return InternalError("cannot find route for change")
    51  	}
    52  
    53  	// POSTs to sideload snaps must be a multipart/form-data file upload.
    54  	form, err := multipart.NewReader(body, boundary).ReadForm(maxReadBuflen)
    55  	if err != nil {
    56  		return BadRequest("cannot read POST form: %v", err)
    57  	}
    58  
    59  	dangerousOK := isTrue(form, "dangerous")
    60  	flags, err := modeFlags(isTrue(form, "devmode"), isTrue(form, "jailmode"), isTrue(form, "classic"))
    61  	if err != nil {
    62  		return BadRequest(err.Error())
    63  	}
    64  
    65  	if len(form.Value["action"]) > 0 && form.Value["action"][0] == "try" {
    66  		if len(form.Value["snap-path"]) == 0 {
    67  			return BadRequest("need 'snap-path' value in form")
    68  		}
    69  		return trySnap(c.d.overlord.State(), form.Value["snap-path"][0], flags)
    70  	}
    71  	flags.RemoveSnapPath = true
    72  
    73  	flags.Unaliased = isTrue(form, "unaliased")
    74  	flags.IgnoreRunning = isTrue(form, "ignore-running")
    75  
    76  	// find the file for the "snap" form field
    77  	var snapBody multipart.File
    78  	var origPath string
    79  out:
    80  	for name, fheaders := range form.File {
    81  		if name != "snap" {
    82  			continue
    83  		}
    84  		for _, fheader := range fheaders {
    85  			snapBody, err = fheader.Open()
    86  			origPath = fheader.Filename
    87  			if err != nil {
    88  				return BadRequest(`cannot open uploaded "snap" file: %v`, err)
    89  			}
    90  			defer snapBody.Close()
    91  
    92  			break out
    93  		}
    94  	}
    95  	defer form.RemoveAll()
    96  
    97  	if snapBody == nil {
    98  		return BadRequest(`cannot find "snap" file field in provided multipart/form-data payload`)
    99  	}
   100  
   101  	// we are in charge of the tempfile life cycle until we hand it off to the change
   102  	changeTriggered := false
   103  	// if you change this prefix, look for it in the tests
   104  	// also see localInstallCleanup in snapstate/snapmgr.go
   105  	tmpf, err := ioutil.TempFile(dirs.SnapBlobDir, dirs.LocalInstallBlobTempPrefix)
   106  	if err != nil {
   107  		return InternalError("cannot create temporary file: %v", err)
   108  	}
   109  
   110  	tempPath := tmpf.Name()
   111  
   112  	defer func() {
   113  		if !changeTriggered {
   114  			os.Remove(tempPath)
   115  		}
   116  	}()
   117  
   118  	if _, err := io.Copy(tmpf, snapBody); err != nil {
   119  		return InternalError("cannot copy request into temporary file: %v", err)
   120  	}
   121  	tmpf.Sync()
   122  
   123  	if len(form.Value["snap-path"]) > 0 {
   124  		origPath = form.Value["snap-path"][0]
   125  	}
   126  
   127  	var instanceName string
   128  
   129  	if len(form.Value["name"]) > 0 {
   130  		// caller has specified desired instance name
   131  		instanceName = form.Value["name"][0]
   132  		if err := snap.ValidateInstanceName(instanceName); err != nil {
   133  			return BadRequest(err.Error())
   134  		}
   135  	}
   136  
   137  	st := c.d.overlord.State()
   138  	st.Lock()
   139  	defer st.Unlock()
   140  
   141  	var snapName string
   142  	var sideInfo *snap.SideInfo
   143  
   144  	if !dangerousOK {
   145  		si, err := snapasserts.DeriveSideInfo(tempPath, assertstate.DB(st))
   146  		switch {
   147  		case err == nil:
   148  			snapName = si.RealName
   149  			sideInfo = si
   150  		case asserts.IsNotFound(err):
   151  			// with devmode we try to find assertions but it's ok
   152  			// if they are not there (implies --dangerous)
   153  			if !isTrue(form, "devmode") {
   154  				msg := "cannot find signatures with metadata for snap"
   155  				if origPath != "" {
   156  					msg = fmt.Sprintf("%s %q", msg, origPath)
   157  				}
   158  				return BadRequest(msg)
   159  			}
   160  			// TODO: set a warning if devmode
   161  		default:
   162  			return BadRequest(err.Error())
   163  		}
   164  	}
   165  
   166  	if snapName == "" {
   167  		// potentially dangerous but dangerous or devmode params were set
   168  		info, err := unsafeReadSnapInfo(tempPath)
   169  		if err != nil {
   170  			return BadRequest("cannot read snap file: %v", err)
   171  		}
   172  		snapName = info.SnapName()
   173  		sideInfo = &snap.SideInfo{RealName: snapName}
   174  	}
   175  
   176  	if instanceName != "" {
   177  		requestedSnapName := snap.InstanceSnap(instanceName)
   178  		if requestedSnapName != snapName {
   179  			return BadRequest(fmt.Sprintf("instance name %q does not match snap name %q", instanceName, snapName))
   180  		}
   181  	} else {
   182  		instanceName = snapName
   183  	}
   184  
   185  	msg := fmt.Sprintf(i18n.G("Install %q snap from file"), instanceName)
   186  	if origPath != "" {
   187  		msg = fmt.Sprintf(i18n.G("Install %q snap from file %q"), instanceName, origPath)
   188  	}
   189  
   190  	tset, _, err := snapstateInstallPath(st, sideInfo, tempPath, instanceName, "", flags)
   191  	if err != nil {
   192  		return errToResponse(err, []string{snapName}, InternalError, "cannot install snap file: %v")
   193  	}
   194  
   195  	chg := newChange(st, "install-snap", msg, []*state.TaskSet{tset}, []string{instanceName})
   196  	chg.Set("api-data", map[string]string{"snap-name": instanceName})
   197  
   198  	ensureStateSoon(st)
   199  
   200  	// only when the unlock succeeds (as opposed to panicing) is the handoff done
   201  	// but this is good enough
   202  	changeTriggered = true
   203  
   204  	return AsyncResponse(nil, &Meta{Change: chg.ID()})
   205  }
   206  
   207  func isTrue(form *multipart.Form, key string) bool {
   208  	value := form.Value[key]
   209  	if len(value) == 0 {
   210  		return false
   211  	}
   212  	b, err := strconv.ParseBool(value[0])
   213  	if err != nil {
   214  		return false
   215  	}
   216  
   217  	return b
   218  }
   219  
   220  func trySnap(st *state.State, trydir string, flags snapstate.Flags) Response {
   221  	st.Lock()
   222  	defer st.Unlock()
   223  
   224  	if !filepath.IsAbs(trydir) {
   225  		return BadRequest("cannot try %q: need an absolute path", trydir)
   226  	}
   227  	if !osutil.IsDirectory(trydir) {
   228  		return BadRequest("cannot try %q: not a snap directory", trydir)
   229  	}
   230  
   231  	// the developer asked us to do this with a trusted snap dir
   232  	info, err := unsafeReadSnapInfo(trydir)
   233  	if _, ok := err.(snap.NotSnapError); ok {
   234  		return SyncResponse(&resp{
   235  			Type: ResponseTypeError,
   236  			Result: &errorResult{
   237  				Message: err.Error(),
   238  				Kind:    client.ErrorKindNotSnap,
   239  			},
   240  			Status: 400,
   241  		}, nil)
   242  	}
   243  	if err != nil {
   244  		return BadRequest("cannot read snap info for %s: %s", trydir, err)
   245  	}
   246  
   247  	tset, err := snapstateTryPath(st, info.InstanceName(), trydir, flags)
   248  	if err != nil {
   249  		return errToResponse(err, []string{info.InstanceName()}, BadRequest, "cannot try %s: %s", trydir)
   250  	}
   251  
   252  	msg := fmt.Sprintf(i18n.G("Try %q snap from %s"), info.InstanceName(), trydir)
   253  	chg := newChange(st, "try-snap", msg, []*state.TaskSet{tset}, []string{info.InstanceName()})
   254  	chg.Set("api-data", map[string]string{"snap-name": info.InstanceName()})
   255  
   256  	ensureStateSoon(st)
   257  
   258  	return AsyncResponse(nil, &Meta{Change: chg.ID()})
   259  }
   260  
   261  var unsafeReadSnapInfo = unsafeReadSnapInfoImpl
   262  
   263  func unsafeReadSnapInfoImpl(snapPath string) (*snap.Info, error) {
   264  	// Condider using DeriveSideInfo before falling back to this!
   265  	snapf, err := snapfile.Open(snapPath)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	return snap.ReadInfoFromSnapFile(snapf, nil)
   270  }