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