github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/daemon/api_validate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  	"sort"
    27  	"strconv"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/asserts/snapasserts"
    31  	"github.com/snapcore/snapd/client"
    32  	"github.com/snapcore/snapd/overlord/assertstate"
    33  	"github.com/snapcore/snapd/overlord/auth"
    34  	"github.com/snapcore/snapd/overlord/snapstate"
    35  	"github.com/snapcore/snapd/overlord/state"
    36  	"github.com/snapcore/snapd/release"
    37  )
    38  
    39  var (
    40  	validationSetsListCmd = &Command{
    41  		Path:       "/v2/validation-sets",
    42  		GET:        listValidationSets,
    43  		ReadAccess: authenticatedAccess{},
    44  	}
    45  
    46  	validationSetsCmd = &Command{
    47  		Path:        "/v2/validation-sets/{account}/{name}",
    48  		GET:         getValidationSet,
    49  		POST:        applyValidationSet,
    50  		ReadAccess:  authenticatedAccess{},
    51  		WriteAccess: authenticatedAccess{},
    52  	}
    53  )
    54  
    55  type validationSetResult struct {
    56  	AccountID string `json:"account-id"`
    57  	Name      string `json:"name"`
    58  	PinnedAt  int    `json:"pinned-at,omitempty"`
    59  	Mode      string `json:"mode,omitempty"`
    60  	Sequence  int    `json:"sequence,omitempty"`
    61  	Valid     bool   `json:"valid"`
    62  	// TODO: attributes for Notes column
    63  }
    64  
    65  func modeString(mode assertstate.ValidationSetMode) (string, error) {
    66  	switch mode {
    67  	case assertstate.Monitor:
    68  		return "monitor", nil
    69  	case assertstate.Enforce:
    70  		return "enforce", nil
    71  	}
    72  	return "", fmt.Errorf("internal error: unhandled mode %d", mode)
    73  }
    74  
    75  func validationSetNotFound(accountID, name string, sequence int) Response {
    76  	v := map[string]interface{}{
    77  		"account-id": accountID,
    78  		"name":       name,
    79  	}
    80  	if sequence != 0 {
    81  		v["sequence"] = sequence
    82  	}
    83  	return &apiError{
    84  		Status:  404,
    85  		Message: "validation set not found",
    86  		Kind:    client.ErrorKindValidationSetNotFound,
    87  		Value:   v,
    88  	}
    89  }
    90  
    91  func listValidationSets(c *Command, r *http.Request, _ *auth.UserState) Response {
    92  	st := c.d.overlord.State()
    93  	st.Lock()
    94  	defer st.Unlock()
    95  
    96  	validationSets, err := assertstate.ValidationSets(st)
    97  	if err != nil {
    98  		return InternalError("accessing validation sets failed: %v", err)
    99  	}
   100  
   101  	names := make([]string, 0, len(validationSets))
   102  	for k := range validationSets {
   103  		names = append(names, k)
   104  	}
   105  	sort.Strings(names)
   106  
   107  	snaps, err := installedSnaps(st)
   108  	if err != nil {
   109  		return InternalError(err.Error())
   110  	}
   111  
   112  	results := make([]validationSetResult, len(names))
   113  	for i, vs := range names {
   114  		tr := validationSets[vs]
   115  		sequence := tr.Current
   116  		if tr.PinnedAt > 0 {
   117  			sequence = tr.PinnedAt
   118  		}
   119  		sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence)
   120  		if err != nil {
   121  			return InternalError("cannot get assertion for validation set tracking %s/%s/%d: %v", tr.AccountID, tr.Name, sequence, err)
   122  		}
   123  		validErr := checkInstalledSnaps(sets, snaps)
   124  		modeStr, err := modeString(tr.Mode)
   125  		if err != nil {
   126  			return InternalError(err.Error())
   127  		}
   128  		results[i] = validationSetResult{
   129  			AccountID: tr.AccountID,
   130  			Name:      tr.Name,
   131  			PinnedAt:  tr.PinnedAt,
   132  			Mode:      modeStr,
   133  			Sequence:  tr.Current,
   134  			Valid:     validErr == nil,
   135  		}
   136  	}
   137  
   138  	return SyncResponse(results)
   139  }
   140  
   141  var checkInstalledSnaps = func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap) error {
   142  	return vsets.CheckInstalledSnaps(snaps)
   143  }
   144  
   145  func installedSnaps(st *state.State) ([]*snapasserts.InstalledSnap, error) {
   146  	var snaps []*snapasserts.InstalledSnap
   147  	all, err := snapstate.All(st)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	for _, snapState := range all {
   152  		cur, err := snapState.CurrentInfo()
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  		snaps = append(snaps,
   157  			snapasserts.NewInstalledSnap(snapState.InstanceName(),
   158  				snapState.CurrentSideInfo().SnapID,
   159  				cur.Revision))
   160  	}
   161  	return snaps, nil
   162  }
   163  
   164  func getValidationSet(c *Command, r *http.Request, user *auth.UserState) Response {
   165  	vars := muxVars(r)
   166  	accountID := vars["account"]
   167  	name := vars["name"]
   168  
   169  	if !asserts.IsValidAccountID(accountID) {
   170  		return BadRequest("invalid account ID %q", accountID)
   171  	}
   172  	if !asserts.IsValidValidationSetName(name) {
   173  		return BadRequest("invalid name %q", name)
   174  	}
   175  
   176  	query := r.URL.Query()
   177  
   178  	// sequence is optional
   179  	sequenceStr := query.Get("sequence")
   180  	var sequence int
   181  	if sequenceStr != "" {
   182  		var err error
   183  		sequence, err = strconv.Atoi(sequenceStr)
   184  		if err != nil {
   185  			return BadRequest("invalid sequence argument")
   186  		}
   187  		if sequence < 0 {
   188  			return BadRequest("invalid sequence argument: %d", sequence)
   189  		}
   190  	}
   191  
   192  	st := c.d.overlord.State()
   193  	st.Lock()
   194  	defer st.Unlock()
   195  
   196  	var tr assertstate.ValidationSetTracking
   197  	err := assertstate.GetValidationSet(st, accountID, name, &tr)
   198  	if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) {
   199  		// not available locally, try to find in the store.
   200  		return validateAgainstStore(st, accountID, name, sequence, user)
   201  	}
   202  	if err != nil {
   203  		return InternalError("accessing validation sets failed: %v", err)
   204  	}
   205  
   206  	modeStr, err := modeString(tr.Mode)
   207  	if err != nil {
   208  		return InternalError(err.Error())
   209  	}
   210  
   211  	// evaluate against installed snaps
   212  
   213  	if tr.PinnedAt > 0 {
   214  		sequence = tr.PinnedAt
   215  	} else {
   216  		sequence = tr.Current
   217  	}
   218  	sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence)
   219  	if err != nil {
   220  		return InternalError(err.Error())
   221  	}
   222  	snaps, err := installedSnaps(st)
   223  	if err != nil {
   224  		return InternalError(err.Error())
   225  	}
   226  
   227  	validErr := checkInstalledSnaps(sets, snaps)
   228  	res := validationSetResult{
   229  		AccountID: tr.AccountID,
   230  		Name:      tr.Name,
   231  		PinnedAt:  tr.PinnedAt,
   232  		Mode:      modeStr,
   233  		Sequence:  tr.Current,
   234  		Valid:     validErr == nil,
   235  	}
   236  	return SyncResponse(res)
   237  }
   238  
   239  type validationSetApplyRequest struct {
   240  	Action   string `json:"action"`
   241  	Mode     string `json:"mode"`
   242  	Sequence int    `json:"sequence,omitempty"`
   243  }
   244  
   245  func applyValidationSet(c *Command, r *http.Request, user *auth.UserState) Response {
   246  	vars := muxVars(r)
   247  	accountID := vars["account"]
   248  	name := vars["name"]
   249  
   250  	if !asserts.IsValidAccountID(accountID) {
   251  		return BadRequest("invalid account ID %q", accountID)
   252  	}
   253  	if !asserts.IsValidValidationSetName(name) {
   254  		return BadRequest("invalid name %q", name)
   255  	}
   256  
   257  	var req validationSetApplyRequest
   258  	decoder := json.NewDecoder(r.Body)
   259  	if err := decoder.Decode(&req); err != nil {
   260  		return BadRequest("cannot decode request body into validation set action: %v", err)
   261  	}
   262  	if decoder.More() {
   263  		return BadRequest("extra content found in request body")
   264  	}
   265  	if req.Sequence < 0 {
   266  		return BadRequest("invalid sequence argument: %d", req.Sequence)
   267  	}
   268  
   269  	st := c.d.overlord.State()
   270  	st.Lock()
   271  	defer st.Unlock()
   272  
   273  	switch req.Action {
   274  	case "forget":
   275  		return forgetValidationSet(st, accountID, name, req.Sequence)
   276  	case "apply":
   277  		return updateValidationSet(st, accountID, name, req.Mode, req.Sequence, user)
   278  	default:
   279  		return BadRequest("unsupported action %q", req.Action)
   280  	}
   281  }
   282  
   283  var validationSetAssertionForMonitor = assertstate.ValidationSetAssertionForMonitor
   284  
   285  // updateValidationSet handles snap validate --monitor and --enforce accountId/name[=sequence].
   286  func updateValidationSet(st *state.State, accountID, name string, reqMode string, sequence int, user *auth.UserState) Response {
   287  	var mode assertstate.ValidationSetMode
   288  	// TODO: only monitor mode for now, add enforce.
   289  	switch reqMode {
   290  	case "monitor":
   291  		mode = assertstate.Monitor
   292  	default:
   293  		return BadRequest("invalid mode %q", reqMode)
   294  	}
   295  
   296  	tr := assertstate.ValidationSetTracking{
   297  		AccountID: accountID,
   298  		Name:      name,
   299  		Mode:      mode,
   300  		// note, Sequence may be 0, meaning not pinned.
   301  		PinnedAt: sequence,
   302  	}
   303  
   304  	userID := 0
   305  	if user != nil {
   306  		userID = user.ID
   307  	}
   308  	pinned := sequence > 0
   309  	opts := assertstate.ResolveOptions{AllowLocalFallback: true}
   310  	as, local, err := validationSetAssertionForMonitor(st, accountID, name, sequence, pinned, userID, &opts)
   311  	if err != nil {
   312  		return BadRequest("cannot get validation set assertion for %v: %v", assertstate.ValidationSetKey(accountID, name), err)
   313  	}
   314  	tr.Current = as.Sequence()
   315  	tr.LocalOnly = local
   316  
   317  	assertstate.UpdateValidationSet(st, &tr)
   318  	return SyncResponse(nil)
   319  }
   320  
   321  // forgetValidationSet forgets the validation set.
   322  // The state needs to be locked by the caller.
   323  func forgetValidationSet(st *state.State, accountID, name string, sequence int) Response {
   324  	// check if it exists first
   325  	var tr assertstate.ValidationSetTracking
   326  	err := assertstate.GetValidationSet(st, accountID, name, &tr)
   327  	if err == state.ErrNoState || (err == nil && sequence != 0 && sequence != tr.PinnedAt) {
   328  		return validationSetNotFound(accountID, name, sequence)
   329  	}
   330  	if err != nil {
   331  		return InternalError("accessing validation sets failed: %v", err)
   332  	}
   333  	assertstate.DeleteValidationSet(st, accountID, name)
   334  	return SyncResponse(nil)
   335  }
   336  
   337  func validationSetForAssert(st *state.State, accountID, name string, sequence int) (*snapasserts.ValidationSets, error) {
   338  	as, err := validationSetAssertFromDb(st, accountID, name, sequence)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	sets := snapasserts.NewValidationSets()
   343  	if err := sets.Add(as); err != nil {
   344  		return nil, err
   345  	}
   346  	return sets, nil
   347  }
   348  
   349  func validationSetAssertFromDb(st *state.State, accountID, name string, sequence int) (*asserts.ValidationSet, error) {
   350  	headers := map[string]string{
   351  		"series":     release.Series,
   352  		"account-id": accountID,
   353  		"name":       name,
   354  		"sequence":   fmt.Sprintf("%d", sequence),
   355  	}
   356  	db := assertstate.DB(st)
   357  	as, err := db.Find(asserts.ValidationSetType, headers)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	vset := as.(*asserts.ValidationSet)
   362  	return vset, nil
   363  }
   364  
   365  func validateAgainstStore(st *state.State, accountID, name string, sequence int, user *auth.UserState) Response {
   366  	// get from the store
   367  	as, err := getSingleSeqFormingAssertion(st, accountID, name, sequence, user)
   368  	if _, ok := err.(*asserts.NotFoundError); ok {
   369  		// not in the store - try to find in the database
   370  		as, err = validationSetAssertFromDb(st, accountID, name, sequence)
   371  		if _, ok := err.(*asserts.NotFoundError); ok {
   372  			return validationSetNotFound(accountID, name, sequence)
   373  		}
   374  	}
   375  	if err != nil {
   376  		return InternalError(err.Error())
   377  	}
   378  	sets := snapasserts.NewValidationSets()
   379  	vset := as.(*asserts.ValidationSet)
   380  	if err := sets.Add(vset); err != nil {
   381  		return InternalError(err.Error())
   382  	}
   383  	snaps, err := installedSnaps(st)
   384  	if err != nil {
   385  		return InternalError(err.Error())
   386  	}
   387  
   388  	validErr := checkInstalledSnaps(sets, snaps)
   389  	res := validationSetResult{
   390  		AccountID: vset.AccountID(),
   391  		Name:      vset.Name(),
   392  		Sequence:  vset.Sequence(),
   393  		// TODO: pass actual err details and implement "verbose" mode
   394  		// for the client?
   395  		Valid: validErr == nil,
   396  	}
   397  	return SyncResponse(res)
   398  }
   399  
   400  func getSingleSeqFormingAssertion(st *state.State, accountID, name string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   401  	sto := snapstate.Store(st, nil)
   402  	at := asserts.Type("validation-set")
   403  	if at == nil {
   404  		panic("validation-set assert type not found")
   405  	}
   406  
   407  	sequenceKey := []string{release.Series, accountID, name}
   408  	st.Unlock()
   409  	as, err := sto.SeqFormingAssertion(at, sequenceKey, sequence, user)
   410  	st.Lock()
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	return as, nil
   416  }