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