github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/image/namefetcher.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 image
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"net/url"
    23  	"time"
    24  
    25  	rktflag "github.com/rkt/rkt/rkt/flag"
    26  
    27  	"github.com/hashicorp/errwrap"
    28  	"github.com/rkt/rkt/pkg/keystore"
    29  	"github.com/rkt/rkt/rkt/config"
    30  	"github.com/rkt/rkt/rkt/pubkey"
    31  	"github.com/rkt/rkt/store/imagestore"
    32  
    33  	"github.com/appc/spec/discovery"
    34  	pgperrors "golang.org/x/crypto/openpgp/errors"
    35  )
    36  
    37  // nameFetcher is used to download images via discovery
    38  type nameFetcher struct {
    39  	InsecureFlags      *rktflag.SecFlags
    40  	S                  *imagestore.Store
    41  	Ks                 *keystore.Keystore
    42  	NoCache            bool
    43  	Debug              bool
    44  	Headers            map[string]config.Headerer
    45  	TrustKeysFromHTTPS bool
    46  }
    47  
    48  // Hash runs the discovery, fetches the image, optionally verifies
    49  // it against passed asc, stores it in the store and returns the hash.
    50  func (f *nameFetcher) Hash(app *discovery.App, a *asc) (string, error) {
    51  	ensureLogger(f.Debug)
    52  	name := app.Name.String()
    53  	diag.Printf("searching for app image %s", name)
    54  	ep, err := f.discoverApp(app)
    55  	if err != nil {
    56  		return "", errwrap.Wrap(fmt.Errorf("discovery failed for %q", name), err)
    57  	}
    58  	latest := false
    59  	// No specified version label, mark it as latest
    60  	if _, ok := app.Labels["version"]; !ok {
    61  		latest = true
    62  	}
    63  	return f.fetchImageFromEndpoints(app, ep, a, latest)
    64  }
    65  
    66  func (f *nameFetcher) discoverApp(app *discovery.App) (discovery.ACIEndpoints, error) {
    67  	insecure := discovery.InsecureNone
    68  	if f.InsecureFlags.SkipTLSCheck() {
    69  		insecure = insecure | discovery.InsecureTLS
    70  	}
    71  	if f.InsecureFlags.AllowHTTP() {
    72  		insecure = insecure | discovery.InsecureHTTP
    73  	}
    74  	hostHeaders := config.ResolveAuthPerHost(f.Headers)
    75  	ep, attempts, err := discovery.DiscoverACIEndpoints(*app, hostHeaders, insecure, 0)
    76  	if f.Debug {
    77  		for _, a := range attempts {
    78  			log.PrintE(fmt.Sprintf("meta tag 'ac-discovery' not found on %s", a.Prefix), a.Error)
    79  		}
    80  	}
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	if len(ep) == 0 {
    85  		return nil, fmt.Errorf("no endpoints discovered")
    86  	}
    87  	return ep, nil
    88  }
    89  
    90  func (f *nameFetcher) fetchImageFromEndpoints(app *discovery.App, ep discovery.ACIEndpoints, a *asc, latest bool) (string, error) {
    91  	ensureLogger(f.Debug)
    92  	// TODO(krnowak): we should probably try all the endpoints,
    93  	// for this we need to clone "a" and call
    94  	// maybeOverrideAscFetcherWithRemote on the clone
    95  	aciURL := ep[0].ACI
    96  	ascURL := ep[0].ASC
    97  	diag.Printf("remote fetching from URL %q", aciURL)
    98  	f.maybeOverrideAscFetcherWithRemote(ascURL, a)
    99  	return f.fetchImageFromSingleEndpoint(app, aciURL, a, latest)
   100  }
   101  
   102  func (f *nameFetcher) fetchImageFromSingleEndpoint(app *discovery.App, aciURL string, a *asc, latest bool) (string, error) {
   103  	diag.Printf("fetching image from %s", aciURL)
   104  
   105  	u, err := url.Parse(aciURL)
   106  	if err != nil {
   107  		return "", errwrap.Wrap(fmt.Errorf("failed to parse URL %q", aciURL), err)
   108  	}
   109  	rem, err := remoteForURL(f.S, u)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  	if !f.NoCache && rem != nil {
   114  		if useCached(rem.DownloadTime, rem.CacheMaxAge) {
   115  			diag.Printf("image for %s isn't expired, not fetching.", aciURL)
   116  			return rem.BlobKey, nil
   117  		}
   118  	}
   119  
   120  	aciFile, cd, err := f.fetch(app, aciURL, a, eTag(rem))
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  	defer aciFile.Close()
   125  
   126  	if key := maybeUseCached(rem, cd); key != "" {
   127  		return key, nil
   128  	}
   129  	key, err := f.S.WriteACI(aciFile, imagestore.ACIFetchInfo{
   130  		Latest: latest,
   131  	})
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	newRem := imagestore.NewRemote(aciURL, a.Location)
   137  	newRem.BlobKey = key
   138  	newRem.DownloadTime = time.Now()
   139  	if cd != nil {
   140  		newRem.ETag = cd.ETag
   141  		newRem.CacheMaxAge = cd.MaxAge
   142  	}
   143  	err = f.S.WriteRemote(newRem)
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  
   148  	return key, nil
   149  }
   150  
   151  func (f *nameFetcher) fetch(app *discovery.App, aciURL string, a *asc, etag string) (readSeekCloser, *cacheData, error) {
   152  	if f.InsecureFlags.SkipTLSCheck() && f.Ks != nil {
   153  		log.Print("warning: TLS verification has been disabled")
   154  	}
   155  	if f.InsecureFlags.SkipImageCheck() && f.Ks != nil {
   156  		log.Print("warning: image signature verification has been disabled")
   157  	}
   158  
   159  	u, err := url.Parse(aciURL)
   160  	if err != nil {
   161  		return nil, nil, errwrap.Wrap(errors.New("error parsing ACI url"), err)
   162  	}
   163  
   164  	if f.InsecureFlags.SkipImageCheck() || f.Ks == nil {
   165  		o := f.httpOps()
   166  		aciFile, cd, err := o.DownloadImageWithETag(u, etag)
   167  		if err != nil {
   168  			return nil, nil, err
   169  		}
   170  		return aciFile, cd, nil
   171  	}
   172  
   173  	return f.fetchVerifiedURL(app, u, a, etag)
   174  }
   175  
   176  func (f *nameFetcher) fetchVerifiedURL(app *discovery.App, u *url.URL, a *asc, etag string) (readSeekCloser, *cacheData, error) {
   177  	var aciFile readSeekCloser // closed on error
   178  	var errClose error         // error signaling to close aciFile
   179  
   180  	appName := app.Name.String()
   181  	f.maybeFetchPubKeys(appName)
   182  
   183  	o := f.httpOps()
   184  	ascFile, retry, err := o.DownloadSignature(a)
   185  	if err != nil {
   186  		return nil, nil, err
   187  	}
   188  	defer ascFile.Close()
   189  
   190  	if !retry {
   191  		if err := f.checkIdentity(appName, ascFile); err != nil {
   192  			return nil, nil, err
   193  		}
   194  	}
   195  
   196  	aciFile, cd, err := o.DownloadImageWithETag(u, etag)
   197  	if err != nil {
   198  		return nil, nil, err
   199  	}
   200  
   201  	defer func() {
   202  		if errClose != nil {
   203  			aciFile.Close()
   204  		}
   205  	}()
   206  
   207  	if cd.UseCached {
   208  		aciFile.Close()
   209  		return NopReadSeekCloser(nil), cd, nil
   210  	}
   211  
   212  	if retry {
   213  		ascFile.Close()
   214  		ascFile, errClose = o.DownloadSignatureAgain(a)
   215  		if errClose != nil {
   216  			ascFile = NopReadSeekCloser(nil)
   217  			return nil, nil, errClose
   218  		}
   219  	}
   220  
   221  	errClose = f.validate(app, aciFile, ascFile)
   222  	if errClose != nil {
   223  		return nil, nil, errClose
   224  	}
   225  
   226  	return aciFile, cd, nil
   227  }
   228  
   229  func (f *nameFetcher) maybeFetchPubKeys(appName string) {
   230  	exists, err := f.Ks.TrustedKeyPrefixExists(appName)
   231  	if err != nil {
   232  		log.Printf("error checking for existing keys: %v", err)
   233  		return
   234  	}
   235  	if exists {
   236  		log.Printf("keys already exist for prefix %q, not fetching again", appName)
   237  		return
   238  	}
   239  	allowHTTP := false
   240  	if f.InsecureFlags.ConsiderInsecurePubKeys() {
   241  		log.Printf("signing keys may be downloaded from an insecure connection")
   242  		allowHTTP = f.InsecureFlags.AllowHTTP()
   243  	}
   244  	if !f.InsecureFlags.SkipTLSCheck() || f.InsecureFlags.ConsiderInsecurePubKeys() {
   245  		m := &pubkey.Manager{
   246  			AuthPerHost:          f.Headers,
   247  			InsecureAllowHTTP:    allowHTTP,
   248  			InsecureSkipTLSCheck: f.InsecureFlags.SkipTLSCheck(),
   249  			TrustKeysFromHTTPS:   f.TrustKeysFromHTTPS,
   250  			Ks:                   f.Ks,
   251  			Debug:                f.Debug,
   252  		}
   253  		pkls, err := m.GetPubKeyLocations(appName)
   254  		// We do not bail out here, because if fetching the
   255  		// public keys fails but we already trust the key, we
   256  		// should be able to run the image anyway.
   257  		if err != nil {
   258  			log.PrintE("error determining key location", err)
   259  		} else {
   260  			accept := pubkey.AcceptAsk
   261  			if f.TrustKeysFromHTTPS {
   262  				accept = pubkey.AcceptForce
   263  			}
   264  			err := m.AddKeys(pkls, appName, accept)
   265  			if err != nil {
   266  				log.PrintE("error adding keys", err)
   267  			}
   268  		}
   269  	}
   270  }
   271  
   272  func (f *nameFetcher) checkIdentity(appName string, ascFile io.ReadSeeker) error {
   273  	if _, err := ascFile.Seek(0, 0); err != nil {
   274  		return errwrap.Wrap(errors.New("error seeking signature file"), err)
   275  	}
   276  	empty := bytes.NewReader([]byte{})
   277  	if _, err := f.Ks.CheckSignature(appName, empty, ascFile); err != nil {
   278  		if err == pgperrors.ErrUnknownIssuer {
   279  			log.Printf("if you expected the signing key to change, try running:")
   280  			log.Printf("    rkt trust --prefix %q", appName)
   281  		}
   282  		if _, ok := err.(pgperrors.SignatureError); !ok {
   283  			return err
   284  		}
   285  	}
   286  	return nil
   287  }
   288  
   289  func (f *nameFetcher) validate(app *discovery.App, aciFile, ascFile io.ReadSeeker) error {
   290  	v, err := newValidator(aciFile)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	if err := v.ValidateName(app.Name.String()); err != nil {
   296  		return err
   297  	}
   298  
   299  	if err := v.ValidateLabels(app.Labels); err != nil {
   300  		return err
   301  	}
   302  
   303  	entity, err := v.ValidateWithSignature(f.Ks, ascFile)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	if _, err := aciFile.Seek(0, 0); err != nil {
   309  		return errwrap.Wrap(errors.New("error seeking ACI file"), err)
   310  	}
   311  
   312  	printIdentities(entity)
   313  	return nil
   314  }
   315  
   316  func (f *nameFetcher) maybeOverrideAscFetcherWithRemote(ascURL string, a *asc) {
   317  	if a.Fetcher != nil {
   318  		return
   319  	}
   320  	a.Location = ascURL
   321  	a.Fetcher = f.httpOps().AscRemoteFetcher()
   322  }
   323  
   324  func (f *nameFetcher) httpOps() *httpOps {
   325  	return &httpOps{
   326  		InsecureSkipTLSVerify: f.InsecureFlags.SkipTLSCheck(),
   327  		S:       f.S,
   328  		Headers: f.Headers,
   329  		Debug:   f.Debug,
   330  	}
   331  }