github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/validate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-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 gadget
    21  
    22  import (
    23  	"fmt"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/osutil"
    28  	"github.com/snapcore/snapd/strutil"
    29  )
    30  
    31  type validationState struct {
    32  	SystemSeed *VolumeStructure
    33  	SystemData *VolumeStructure
    34  	SystemBoot *VolumeStructure
    35  	SystemSave *VolumeStructure
    36  }
    37  
    38  // ValidationConstraints carries extra constraints on top of those
    39  // implied by the model to use for gadget validation.
    40  // They might be constraints that are determined only at runtime.
    41  type ValidationConstraints struct {
    42  	// EncryptedData when true indicates that the gadget will be used on a
    43  	// device where the data partition will be encrypted.
    44  	EncryptedData bool
    45  }
    46  
    47  // Validate validates the given gadget metadata against the consistency rules
    48  // for role usage, labels etc as implied by the model and extra constraints
    49  // that might be known only at runtime.
    50  func Validate(info *Info, model Model, extra *ValidationConstraints) error {
    51  	if err := ruleValidateVolumes(info.Volumes, model); err != nil {
    52  		return err
    53  	}
    54  	if extra != nil {
    55  		if extra.EncryptedData {
    56  			if err := validateEncryptionSupport(info); err != nil {
    57  				return fmt.Errorf("gadget does not support encrypted data: %v", err)
    58  			}
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  func validateEncryptionSupport(info *Info) error {
    65  	for name, vol := range info.Volumes {
    66  		var haveSave bool
    67  		for _, s := range vol.Structure {
    68  			if s.Role == SystemSave {
    69  				haveSave = true
    70  			}
    71  		}
    72  		if !haveSave {
    73  			return fmt.Errorf("volume %q has no structure with system-save role", name)
    74  		}
    75  		// TODO:UC20: shall we make sure that size of ubuntu-save is reasonable?
    76  	}
    77  	return nil
    78  }
    79  
    80  func ruleValidateVolumes(vols map[string]*Volume, model Model) error {
    81  	for name, v := range vols {
    82  		if err := ruleValidateVolume(name, v, model); err != nil {
    83  			return fmt.Errorf("invalid volume %q: %v", name, err)
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  func ruleValidateVolume(name string, vol *Volume, model Model) error {
    90  	state := &validationState{}
    91  
    92  	for idx, s := range vol.Structure {
    93  		if err := ruleValidateVolumeStructure(&s); err != nil {
    94  			return fmt.Errorf("invalid structure %v: %v", fmtIndexAndName(idx, s.Name), err)
    95  		}
    96  
    97  		// XXX what about implicit roles?
    98  		switch s.Role {
    99  		case SystemSeed:
   100  			if state.SystemSeed != nil {
   101  				return fmt.Errorf("cannot have more than one partition with system-seed role")
   102  			}
   103  			state.SystemSeed = &vol.Structure[idx]
   104  		case SystemData:
   105  			if state.SystemData != nil {
   106  				return fmt.Errorf("cannot have more than one partition with system-data role")
   107  			}
   108  			state.SystemData = &vol.Structure[idx]
   109  		case SystemBoot:
   110  			if state.SystemBoot != nil {
   111  				return fmt.Errorf("cannot have more than one partition with system-boot role")
   112  			}
   113  			state.SystemBoot = &vol.Structure[idx]
   114  		case SystemSave:
   115  			if state.SystemSave != nil {
   116  				return fmt.Errorf("cannot have more than one partition with system-save role")
   117  			}
   118  			state.SystemSave = &vol.Structure[idx]
   119  		}
   120  
   121  	}
   122  
   123  	if err := ensureVolumeRuleConsistency(state, model); err != nil {
   124  		return err
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  func ruleValidateVolumeStructure(vs *VolumeStructure) error {
   131  	if err := validateReservedLabels(vs); err != nil {
   132  		return err
   133  	}
   134  	return nil
   135  }
   136  
   137  var (
   138  	reservedLabels = []string{
   139  		// 2020-12-02 disabled because of customer gadget hotfix
   140  		/*ubuntuBootLabel,*/
   141  		ubuntuSeedLabel,
   142  		ubuntuDataLabel,
   143  		ubuntuSaveLabel,
   144  	}
   145  )
   146  
   147  func validateReservedLabels(vs *VolumeStructure) error {
   148  	if vs.Role != "" {
   149  		// structure specifies a role, its labels will be checked later
   150  		return nil
   151  	}
   152  	if vs.Label == "" {
   153  		return nil
   154  	}
   155  	if strutil.ListContains(reservedLabels, vs.Label) {
   156  		// a structure without a role uses one of reserved labels
   157  		return fmt.Errorf("label %q is reserved", vs.Label)
   158  	}
   159  	return nil
   160  }
   161  
   162  func ensureVolumeRuleConsistencyNoConstraints(state *validationState) error {
   163  	switch {
   164  	case state.SystemSeed == nil && state.SystemData == nil:
   165  		// happy so far
   166  	case state.SystemSeed != nil && state.SystemData == nil:
   167  		return fmt.Errorf("the system-seed role requires system-data to be defined")
   168  	case state.SystemSeed == nil && state.SystemData != nil:
   169  		if state.SystemData.Label != "" && state.SystemData.Label != implicitSystemDataLabel {
   170  			return fmt.Errorf("system-data structure must have an implicit label or %q, not %q", implicitSystemDataLabel, state.SystemData.Label)
   171  		}
   172  	case state.SystemSeed != nil && state.SystemData != nil:
   173  		if err := checkSeedDataImplicitLabels(state); err != nil {
   174  			return err
   175  		}
   176  	}
   177  	if state.SystemSave != nil {
   178  		if err := ensureSystemSaveRuleConsistency(state); err != nil {
   179  			return err
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  func ensureVolumeRuleConsistencyWithConstraints(state *validationState, model Model) error {
   186  	// TODO: should we validate usage of uc20 specific system-recovery-{image,select}
   187  	//       roles too? they should only be used on uc20 systems, so models that
   188  	//       have a grade set and are not classic
   189  
   190  	switch {
   191  	case state.SystemSeed == nil && state.SystemData == nil:
   192  		if wantsSystemSeed(model) {
   193  			return fmt.Errorf("model requires system-seed partition, but no system-seed or system-data partition found")
   194  		}
   195  	case state.SystemSeed != nil && state.SystemData == nil:
   196  		return fmt.Errorf("the system-seed role requires system-data to be defined")
   197  	case state.SystemSeed == nil && state.SystemData != nil:
   198  		// error if we have the SystemSeed constraint but no actual system-seed structure
   199  		if wantsSystemSeed(model) {
   200  			return fmt.Errorf("model requires system-seed structure, but none was found")
   201  		}
   202  		// without SystemSeed, system-data label must be implicit or writable
   203  		if err := checkImplicitLabel(SystemData, state.SystemData, implicitSystemDataLabel); err != nil {
   204  			return err
   205  		}
   206  	case state.SystemSeed != nil && state.SystemData != nil:
   207  		// error if we don't have the SystemSeed constraint but we have a system-seed structure
   208  		if !wantsSystemSeed(model) {
   209  			return fmt.Errorf("model does not support the system-seed role")
   210  		}
   211  		if err := checkSeedDataImplicitLabels(state); err != nil {
   212  			return err
   213  		}
   214  	}
   215  	if state.SystemSave != nil {
   216  		if err := ensureSystemSaveRuleConsistency(state); err != nil {
   217  			return err
   218  		}
   219  	}
   220  	return nil
   221  }
   222  
   223  func checkImplicitLabel(role string, vs *VolumeStructure, implicitLabel string) error {
   224  	if vs.Label != "" && vs.Label != implicitLabel {
   225  		return fmt.Errorf("%s structure must have an implicit label or %q, not %q", role, implicitLabel, vs.Label)
   226  
   227  	}
   228  	return nil
   229  }
   230  
   231  func ensureVolumeRuleConsistency(state *validationState, model Model) error {
   232  	if model == nil {
   233  		return ensureVolumeRuleConsistencyNoConstraints(state)
   234  	}
   235  	return ensureVolumeRuleConsistencyWithConstraints(state, model)
   236  }
   237  
   238  func checkSeedDataImplicitLabels(state *validationState) error {
   239  	if err := checkImplicitLabel(SystemData, state.SystemData, ubuntuDataLabel); err != nil {
   240  		return err
   241  	}
   242  	if err := checkImplicitLabel(SystemSeed, state.SystemSeed, ubuntuSeedLabel); err != nil {
   243  		return err
   244  	}
   245  	return nil
   246  }
   247  
   248  func ensureSystemSaveRuleConsistency(state *validationState) error {
   249  	if state.SystemData == nil || state.SystemSeed == nil {
   250  		return fmt.Errorf("system-save requires system-seed and system-data structures")
   251  	}
   252  	if err := checkImplicitLabel(SystemSave, state.SystemSave, ubuntuSaveLabel); err != nil {
   253  		return err
   254  	}
   255  	return nil
   256  }
   257  
   258  // content validation
   259  
   260  func validateVolumeContentsPresence(gadgetSnapRootDir string, vol *LaidOutVolume) error {
   261  	// bare structure content is checked to exist during layout
   262  	// make sure that filesystem content source paths exist as well
   263  	for _, s := range vol.LaidOutStructure {
   264  		if !s.HasFilesystem() {
   265  			continue
   266  		}
   267  		for _, c := range s.Content {
   268  			// TODO: detect and skip Content with "$kernel:" style refs if there is no kernelSnapRootDir passed in as well
   269  			realSource := filepath.Join(gadgetSnapRootDir, c.UnresolvedSource)
   270  			if !osutil.FileExists(realSource) {
   271  				return fmt.Errorf("structure %v, content %v: source path does not exist", s, c)
   272  			}
   273  			if strings.HasSuffix(c.ResolvedSource(), "/") {
   274  				// expecting a directory
   275  				if err := checkSourceIsDir(realSource + "/"); err != nil {
   276  					return fmt.Errorf("structure %v, content %v: %v", s, c, err)
   277  				}
   278  			}
   279  		}
   280  	}
   281  	return nil
   282  }
   283  
   284  // ValidateContent checks whether the given directory contains valid matching content with respect to the given pre-validated gadget metadata.
   285  func ValidateContent(info *Info, gadgetSnapRootDir string) error {
   286  	// TODO: also validate that only one "<bl-name>.conf" file is
   287  	// in the root directory of the gadget snap, because the
   288  	// "<bl-name>.conf" file indicates precisely which bootloader
   289  	// the gadget uses and as such there cannot be more than one
   290  	// such bootloader
   291  	for name, vol := range info.Volumes {
   292  		lv, err := LayoutVolume(gadgetSnapRootDir, vol, defaultConstraints)
   293  		if err != nil {
   294  			return fmt.Errorf("invalid layout of volume %q: %v", name, err)
   295  		}
   296  		if err := validateVolumeContentsPresence(gadgetSnapRootDir, lv); err != nil {
   297  			return fmt.Errorf("invalid volume %q: %v", name, err)
   298  		}
   299  	}
   300  	return nil
   301  }