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 }