github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/stage1hash.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "errors" 19 "fmt" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "github.com/appc/spec/schema/types" 25 "github.com/hashicorp/errwrap" 26 "github.com/rkt/rkt/rkt/config" 27 "github.com/rkt/rkt/rkt/image" 28 "github.com/rkt/rkt/store/imagestore" 29 "github.com/rkt/rkt/store/treestore" 30 "github.com/spf13/pflag" 31 ) 32 33 // stage1ImageLocationKind describes the stage1 image location 34 type stage1ImageLocationKind int 35 36 const ( 37 // location unset, it is not a valid kind to be used 38 stage1ImageLocationUnset stage1ImageLocationKind = iota 39 // a URL with a scheme 40 stage1ImageLocationURL 41 // an absolute or a relative path 42 stage1ImageLocationPath 43 // an image name 44 stage1ImageLocationName 45 // an image hash 46 stage1ImageLocationHash 47 // an image in the default dir 48 stage1ImageLocationFromDir 49 ) 50 51 // stage1FlagData is used for creating the flags for each valid location kind 52 type stage1FlagData struct { 53 kind stage1ImageLocationKind 54 flag string 55 name string 56 help string 57 } 58 59 // stage1ImageLocation is used to store the user's choice of stage1 image via flags 60 type stage1ImageLocation struct { 61 kind stage1ImageLocationKind 62 location string 63 } 64 65 // stage1ImageLocationFlag is an implementation of a pflag.Value 66 // interface, which handles all the valid location kinds 67 type stage1ImageLocationFlag struct { 68 loc *stage1ImageLocation 69 kind stage1ImageLocationKind 70 } 71 72 func (f *stage1ImageLocationFlag) Set(location string) error { 73 if f.loc.kind != stage1ImageLocationUnset { 74 wanted := stage1FlagsData[f.kind] 75 current := stage1FlagsData[f.loc.kind] 76 if f.loc.kind == f.kind { 77 return fmt.Errorf("--%s already used", current.flag) 78 } 79 return fmt.Errorf("flags --%s and --%s are mutually exclusive", 80 wanted.flag, current.flag) 81 } 82 f.loc.kind = f.kind 83 f.loc.location = location 84 return nil 85 } 86 87 func (f *stage1ImageLocationFlag) String() string { 88 return f.loc.location 89 } 90 91 func (f *stage1ImageLocationFlag) Type() string { 92 return stage1FlagsData[f.kind].name 93 } 94 95 var ( 96 // defaults defined by configure, set by linker 97 // default stage1 image name 98 // (e.g. coreos.com/rkt/stage1-coreos) 99 buildDefaultStage1Name string 100 // default stage1 image version (e.g. 0.15.0) 101 buildDefaultStage1Version string 102 // an absolute path or a URL to the default stage1 image file 103 buildDefaultStage1ImageLoc string 104 // filename of the default stage1 image file in the default 105 // stage1 images directory 106 buildDefaultStage1ImageInRktDir string 107 // an absolute path to the stage1 images directory 108 buildDefaultStage1ImagesDir string 109 110 // this holds necessary data to generate the --stage1-* flags 111 // for each location kind 112 stage1FlagsData = map[stage1ImageLocationKind]*stage1FlagData{ 113 stage1ImageLocationURL: { 114 kind: stage1ImageLocationURL, 115 flag: "stage1-url", 116 name: "stage1URL", 117 help: "URL to an image to use as stage1", 118 }, 119 120 stage1ImageLocationPath: { 121 kind: stage1ImageLocationPath, 122 flag: "stage1-path", 123 name: "stage1Path", 124 help: "absolute or relative path to an image to use as stage1", 125 }, 126 127 stage1ImageLocationName: { 128 kind: stage1ImageLocationName, 129 flag: "stage1-name", 130 name: "stage1Name", 131 help: "name of an image to use as stage1", 132 }, 133 134 stage1ImageLocationHash: { 135 kind: stage1ImageLocationHash, 136 flag: "stage1-hash", 137 name: "stage1Hash", 138 help: "hash of an image to use as stage1", 139 }, 140 141 stage1ImageLocationFromDir: { 142 kind: stage1ImageLocationFromDir, 143 flag: "stage1-from-dir", 144 name: "stage1FromDir", 145 help: "filename of an image in stage1 images directory to use as stage1", 146 }, 147 } 148 // location to stage1 image overridden by one of --stage1-* 149 // flags 150 overriddenStage1Location = stage1ImageLocation{ 151 kind: stage1ImageLocationUnset, 152 location: "", 153 } 154 ) 155 156 // addStage1ImageFlags adds flags for specifying custom stage1 image 157 func addStage1ImageFlags(flags *pflag.FlagSet) { 158 for _, data := range stage1FlagsData { 159 wrapper := &stage1ImageLocationFlag{ 160 loc: &overriddenStage1Location, 161 kind: data.kind, 162 } 163 flags.Var(wrapper, data.flag, data.help) 164 } 165 } 166 167 // getStage1Hash will try to get the hash of stage1 to use. 168 // 169 // Before getting inside this rats nest, let's try to write up the 170 // expected behaviour. 171 // 172 // If the user passed --stage1-url, --stage1-path, --stage1-name, 173 // --stage1-hash, or --stage1-from-dir then we take what was passed 174 // and try to load it. If it failed, we bail out. No second chances 175 // and whatnot. The details about how each flag type should be handled 176 // are below. 177 // 178 // For --stage1-url, we do discovery, and try to fetch it directly into 179 // the store. 180 // 181 // For --stage1-path, we do no discovery and try to fetch the image 182 // directly from the given file path into the store. If the file is not 183 // found, then we try to fetch the file in the same directory as the 184 // rkt binary itself into the store. 185 // 186 // For --stage1-from-dir, we do no discovery and try to fetch the image 187 // from the stage1 images directory by the name. 188 // 189 // For --stage1-name, we do discovery and fetch the discovered image 190 // into the store. 191 // 192 // For --stage1-hash, we do no discovery and try to fetch the image 193 // from the store by the hash. 194 // 195 // If the user passed none of the above flags, we try to get the name, 196 // the version and the location from the configuration. The name and 197 // the version must be defined in pair, that is - either both of them 198 // are defined in configuration or none. Values from the configuration 199 // override the values taken from the configure script. We search for 200 // an image with the default name and version in the store. If it is 201 // there, then woo, we are done. Otherwise we get the location and try 202 // to load it. Depending on location type, we bail out immediately or 203 // get a second chance. 204 // 205 // Details about the handling of different location types follow. 206 // 207 // If location is a URL, we do no discovery, just try to fetch it 208 // directly into the store instead. 209 // 210 // If location is a path, we do no discovery, just try to fetch it 211 // directly into the store instead. If the file is not found and we 212 // have a second chance, we try to fetch the file in the same 213 // directory as the rkt binary itself into the store. 214 // 215 // If location is an image hash then we make sure that it exists in 216 // the store. 217 func getStage1Hash(s *imagestore.Store, ts *treestore.Store, c *config.Config) (*types.Hash, error) { 218 imgDir := getStage1ImagesDirectory(c) 219 if overriddenStage1Location.kind != stage1ImageLocationUnset { 220 // we passed a --stage-{url,path,name,hash,from-dir} flag 221 return getStage1HashFromFlag(s, ts, overriddenStage1Location, imgDir) 222 } 223 224 imgRef, imgLoc, imgFileName := getStage1DataFromConfig(c) 225 return getConfiguredStage1Hash(s, ts, imgRef, imgLoc, imgFileName) 226 } 227 228 func getStage1ImagesDirectory(c *config.Config) string { 229 if c.Paths.Stage1ImagesDir != "" { 230 return c.Paths.Stage1ImagesDir 231 } 232 return buildDefaultStage1ImagesDir 233 } 234 235 func getStage1HashFromFlag(s *imagestore.Store, ts *treestore.Store, loc stage1ImageLocation, dir string) (*types.Hash, error) { 236 withKeystore := true 237 location := loc.location 238 if loc.kind == stage1ImageLocationFromDir { 239 location = filepath.Join(dir, loc.location) 240 } 241 trustedLocation, err := isTrustedLocation(location) 242 if err != nil { 243 return nil, err 244 } 245 246 switch loc.kind { 247 case stage1ImageLocationURL, stage1ImageLocationPath, stage1ImageLocationFromDir: 248 if trustedLocation { 249 withKeystore = false 250 } 251 } 252 253 fn := getStage1Finder(s, ts, withKeystore) 254 return fn.FindImage(location, "") 255 } 256 257 func getStage1DataFromConfig(c *config.Config) (string, string, string) { 258 imgName := c.Stage1.Name 259 imgVersion := c.Stage1.Version 260 // if the name in the configuration is empty, then the version 261 // is empty too, but let's better be safe now then sorry later 262 // - if either one is empty we take build defaults for both 263 if imgName == "" || imgVersion == "" { 264 imgName = buildDefaultStage1Name 265 imgVersion = buildDefaultStage1Version 266 } 267 imgRef := fmt.Sprintf("%s:%s", imgName, imgVersion) 268 269 imgLoc := c.Stage1.Location 270 imgFileName := getFileNameFromLocation(imgLoc) 271 if imgLoc == "" { 272 imgLoc = buildDefaultStage1ImageLoc 273 imgFileName = buildDefaultStage1ImageInRktDir 274 } 275 276 return imgRef, imgLoc, imgFileName 277 } 278 279 func getFileNameFromLocation(imgLoc string) string { 280 if !filepath.IsAbs(imgLoc) { 281 return "" 282 } 283 return filepath.Base(imgLoc) 284 } 285 286 func isTrustedLocation(location string) (bool, error) { 287 absLocation, err := filepath.Abs(location) 288 if err != nil { 289 return false, err 290 } 291 if absLocation == buildDefaultStage1ImageLoc || 292 strings.HasPrefix(absLocation, fmt.Sprintf("%s%c", filepath.Clean(buildDefaultStage1ImagesDir), filepath.Separator)) { 293 return true, nil 294 } 295 return false, nil 296 } 297 298 func getConfiguredStage1Hash(s *imagestore.Store, ts *treestore.Store, imgRef, imgLoc, imgFileName string) (*types.Hash, error) { 299 trusted, err := isTrustedLocation(imgLoc) 300 if err != nil { 301 return nil, err 302 } 303 fn := getStage1Finder(s, ts, !trusted) 304 if !strings.HasSuffix(imgRef, "-dirty") { 305 oldPolicy := fn.PullPolicy 306 fn.PullPolicy = image.PullPolicyNever 307 if hash, err := fn.FindImage(imgRef, ""); err == nil { 308 return hash, nil 309 } 310 fn.PullPolicy = oldPolicy 311 } 312 if imgLoc == "" && imgFileName == "" { 313 return nil, fmt.Errorf("neither the location of the default stage1 image nor its filename are set, use --stage1-{path,url,name,hash,from-dir} flag") 314 } 315 // If imgLoc is not an absolute path, then it is a URL 316 imgLocIsURL := imgLoc != "" && !filepath.IsAbs(imgLoc) 317 if imgLocIsURL { 318 return fn.FindImage(imgLoc, "") 319 } 320 return getStage1HashFromPath(fn, imgLoc, imgFileName) 321 } 322 323 func getStage1Finder(s *imagestore.Store, ts *treestore.Store, withKeystore bool) *image.Finder { 324 fn := &image.Finder{ 325 S: s, 326 Ts: ts, 327 Debug: globalFlags.Debug, 328 InsecureFlags: globalFlags.InsecureFlags, 329 TrustKeysFromHTTPS: globalFlags.TrustKeysFromHTTPS, 330 331 PullPolicy: image.PullPolicyNew, 332 WithDeps: false, 333 } 334 335 if withKeystore { 336 fn.Ks = getKeystore() 337 } 338 return fn 339 } 340 341 func getStage1HashFromPath(fn *image.Finder, imgLoc, imgFileName string) (*types.Hash, error) { 342 var fetchErr error 343 var fallbackErr error 344 if imgLoc != "" { 345 hash, err := fn.FindImage(imgLoc, "") 346 if err == nil { 347 return hash, nil 348 } 349 fetchErr = err 350 } 351 if imgFileName != "" { 352 exePath, err := os.Readlink("/proc/self/exe") 353 if err != nil { 354 fallbackErr = err 355 } else { 356 // using stage1 image in rkt's path, don't check the signature 357 fn.Ks = nil 358 rktDir := filepath.Dir(exePath) 359 imgPath := filepath.Join(rktDir, imgFileName) 360 hash, err := fn.FindImage(imgPath, "") 361 if err == nil { 362 return hash, nil 363 } 364 fallbackErr = err 365 } 366 } 367 return nil, mergeStage1Errors(fetchErr, fallbackErr) 368 } 369 370 func mergeStage1Errors(fetchErr, fallbackErr error) error { 371 if fetchErr != nil && fallbackErr != nil { 372 innerErr := errwrap.Wrap(fallbackErr, fetchErr) 373 return errwrap.Wrap(errors.New("failed to fetch stage1 image and failed to fall back to stage1 image in the rkt directory"), innerErr) 374 } else if fetchErr != nil { 375 return errwrap.Wrap(errors.New("failed to fetch stage1 image"), fetchErr) 376 } 377 return errwrap.Wrap(errors.New("failed to fall back to stage1 image in rkt directory (default stage1 image location is not specified)"), fallbackErr) 378 }