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  }