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 }