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 }