diff --git a/rkt/fetch.go b/rkt/fetch.go index e4d6107..b45c177 100644 --- a/rkt/fetch.go +++ b/rkt/fetch.go @@ -21,6 +21,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" "runtime" "strings" "time" @@ -71,7 +72,7 @@ func runFetch(args []string) (exit int) { ks := getKeystore() for _, img := range args { - hash, err := fetchImage(img, ds, ks, true) + hash, err := fetchImage(img, "" /* TODO(vc): wire to --signature */, ds, ks, true) if err != nil { stderr("%v", err) return 1 @@ -85,9 +86,39 @@ func runFetch(args []string) (exit int) { // fetchImage will take an image as either a URL or a name string and import it // into the store if found. If discover is true meta-discovery is enabled. -func fetchImage(img string, ds *cas.Store, ks *keystore.Keystore, discover bool) (string, error) { +// If asc is not "", it must exist as a local file and will be used as the +// signature file for verification, unless verification is disabled. +func fetchImage(img string, asc string, ds *cas.Store, ks *keystore.Keystore, discover bool) (string, error) { + var ( + ascFile *os.File + err error + ) + if asc != "" && ks != nil { + ascFile, err = os.Open(asc) + if err != nil { + return "", fmt.Errorf("unable to open signature file: %v", err) + } + defer ascFile.Close() + } + u, err := url.Parse(img) - if err == nil && discover && u.Scheme == "" { + if err != nil { + return "", fmt.Errorf("not a valid image reference (%s)", img) + } + + // if img refers to a local file, ensure the scheme is file:// and make the url path absolute + _, err = os.Stat(u.Path) + if err == nil { + u.Path, err = filepath.Abs(u.Path) + if err != nil { + return "", fmt.Errorf("unable to get abs path: %v", err) + } + u.Scheme = "file" + } else if !os.IsNotExist(err) { + return "", fmt.Errorf("unable to access %q: %v", img, err) + } + + if discover && u.Scheme == "" { if app := newDiscoveryApp(img); app != nil { stdout("rkt: searching for app image %s", img) ep, attempts, err := discovery.DiscoverEndpoints(*app, true) @@ -111,45 +142,53 @@ func fetchImage(img string, ds *cas.Store, ks *keystore.Keystore, discover bool) if _, ok := app.Labels["version"]; !ok { latest = true } - return fetchImageFromEndpoints(ep, ds, ks, latest) + return fetchImageFromEndpoints(ep, ascFile, ds, ks, latest) } } - if err != nil { - return "", fmt.Errorf("not a valid URL (%s)", img) - } + switch u.Scheme { - case "http", "https", "docker": + case "http", "https", "docker", "file": default: return "", fmt.Errorf("rkt only supports http, https or docker URLs (%s)", img) } - return fetchImageFromURL(u.String(), u.Scheme, ds, ks, false) + return fetchImageFromURL(u.String(), u.Scheme, ascFile, ds, ks, false) } -func fetchImageFromEndpoints(ep *discovery.Endpoints, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { - return downloadImage(ep.ACIEndpoints[0].ACI, ep.ACIEndpoints[0].ASC, "", ds, ks, latest) +func fetchImageFromEndpoints(ep *discovery.Endpoints, ascFile *os.File, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { + return fetchImageFrom(ep.ACIEndpoints[0].ACI, ep.ACIEndpoints[0].ASC, "", ascFile, ds, ks, latest) } -func fetchImageFromURL(imgurl string, scheme string, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { - return downloadImage(imgurl, ascURLFromImgURL(imgurl), scheme, ds, ks, latest) +func fetchImageFromURL(imgurl string, scheme string, ascFile *os.File, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { + return fetchImageFrom(imgurl, ascURLFromImgURL(imgurl), scheme, ascFile, ds, ks, latest) } -func downloadImage(aciURL string, ascURL string, scheme string, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { - stdout("rkt: fetching image from %s", aciURL) +func fetchImageFrom(aciURL string, ascURL string, scheme string, ascFile *os.File, ds *cas.Store, ks *keystore.Keystore, latest bool) (string, error) { + if scheme != "file" || globalFlags.Debug { + stdout("rkt: fetching image from %s", aciURL) + } + if globalFlags.InsecureSkipVerify { - stdout("rkt: warning: signature verification has been disabled") + if ks != nil { + stdout("rkt: warning: signature verification has been disabled") + } } else if scheme == "docker" { return "", fmt.Errorf("signature verification for docker images is not supported (try --insecure-skip-verify)") } + var key string rem, ok, err := ds.GetRemote(aciURL) - if err != nil { + if err == nil { + key = rem.BlobKey + } else { return "", err } if !ok { - entity, aciFile, err := download(aciURL, ascURL, ds, ks) + entity, aciFile, err := fetch(aciURL, ascURL, ascFile, ds, ks) if err != nil { return "", err } - defer os.Remove(aciFile.Name()) + if scheme != "file" { + defer os.Remove(aciFile.Name()) + } if entity != nil && !globalFlags.InsecureSkipVerify { fmt.Println("rkt: signature verified: ") @@ -157,26 +196,29 @@ func downloadImage(aciURL string, ascURL string, scheme string, ds *cas.Store, k stdout(" %s", v.Name) } } - key, err := ds.WriteACI(aciFile, latest) - if err != nil { - return "", err - } - rem = cas.NewRemote(aciURL, ascURL) - rem.BlobKey = key - err = ds.WriteRemote(rem) + key, err = ds.WriteACI(aciFile, latest) if err != nil { return "", err } + if scheme != "file" { + rem = cas.NewRemote(aciURL, ascURL) + rem.BlobKey = key + err = ds.WriteRemote(rem) + if err != nil { + return "", err + } + } } - return rem.BlobKey, nil + return key, nil } -// download downloads and verifies the remote ACI from the given aciURL. -// If Keystore is nil signature verification will be skipped. -// download returns the signer, an *os.File representing the ACI, and an error if any. -// err will be nil if the ACI downloads successfully and the ACI is verified. -func download(aciURL string, ascURL string, ds *cas.Store, ks *keystore.Keystore) (*openpgp.Entity, *os.File, error) { +// fetch opens/downloads and verifies the remote ACI. +// If ascFile is not nil, it will be used as the signature file and ascURL will be ignored. +// If Keystore is nil signature verification will be skipped, regardless of ascFile. +// fetch returns the signer, an *os.File representing the ACI, and an error if any. +// err will be nil if the ACI fetches successfully and the ACI is verified. +func fetch(aciURL string, ascURL string, ascFile *os.File, ds *cas.Store, ks *keystore.Keystore) (*openpgp.Entity, *os.File, error) { var entity *openpgp.Entity u, err := url.Parse(aciURL) if err != nil { @@ -203,51 +245,69 @@ func download(aciURL string, ascURL string, ds *cas.Store, ks *keystore.Keystore return nil, aciFile, nil } - var sigTempFile *os.File - if ks != nil { - stdout("Downloading signature from %v\n", ascURL) - sigTempFile, err = ds.TmpFile() + if ks != nil && ascFile == nil { + u, err := url.Parse(ascURL) if err != nil { - return nil, nil, fmt.Errorf("error setting up temporary file: %v", err) + return nil, nil, fmt.Errorf("error parsing ASC url: %v", err) } - defer sigTempFile.Close() - defer os.Remove(sigTempFile.Name()) - err = downloadSignatureFile(ascURL, sigTempFile) - if err != nil { - return nil, nil, fmt.Errorf("error downloading the signature file: %v", err) + if u.Scheme == "file" { + ascFile, err = os.Open(u.Path) + if err != nil { + return nil, nil, fmt.Errorf("error opening signature file: %v", err) + } + } else { + stdout("Downloading signature from %v\n", ascURL) + ascFile, err = ds.TmpFile() + if err != nil { + return nil, nil, fmt.Errorf("error setting up temporary file: %v", err) + } + if err = downloadSignatureFile(ascURL, ascFile); err != nil { + return nil, nil, fmt.Errorf("error downloading the signature file: %v", err) + } + defer os.Remove(ascFile.Name()) } + defer ascFile.Close() } - acif, err := ds.TmpFile() - if err != nil { - return nil, acif, fmt.Errorf("error setting up temporary file: %v", err) - } - err = downloadACI(aciURL, acif) - if err != nil { - return nil, acif, fmt.Errorf("error downloading ACI: %v", err) + var aciFile *os.File + if u.Scheme == "file" { + aciFile, err = os.Open(u.Path) + if err != nil { + return nil, nil, fmt.Errorf("error opening ACI file: %v", err) + } + } else { + aciFile, err = ds.TmpFile() + if err != nil { + return nil, aciFile, fmt.Errorf("error setting up temporary file: %v", err) + } + defer os.Remove(aciFile.Name()) + + if err = downloadACI(aciURL, aciFile); err != nil { + return nil, nil, fmt.Errorf("error downloading ACI: %v", err) + } } if ks != nil { - manifest, err := aci.ManifestFromImage(acif) + manifest, err := aci.ManifestFromImage(aciFile) if err != nil { - return nil, acif, err + return nil, aciFile, err } - if _, err := acif.Seek(0, 0); err != nil { - return nil, acif, fmt.Errorf("error seeking ACI file: %v", err) + if _, err := aciFile.Seek(0, 0); err != nil { + return nil, aciFile, fmt.Errorf("error seeking ACI file: %v", err) } - if _, err := sigTempFile.Seek(0, 0); err != nil { - return nil, acif, fmt.Errorf("error seeking signature file: %v", err) + if _, err := ascFile.Seek(0, 0); err != nil { + return nil, aciFile, fmt.Errorf("error seeking signature file: %v", err) } - if entity, err = ks.CheckSignature(manifest.Name.String(), acif, sigTempFile); err != nil { - return nil, acif, err + if entity, err = ks.CheckSignature(manifest.Name.String(), aciFile, ascFile); err != nil { + return nil, aciFile, err } } - if _, err := acif.Seek(0, 0); err != nil { - return nil, acif, err + if _, err := aciFile.Seek(0, 0); err != nil { + return nil, aciFile, fmt.Errorf("error seeking ACI file: %v", err) } - return entity, acif, nil + return entity, aciFile, nil } type writeSyncer interface { diff --git a/rkt/fetch_test.go b/rkt/fetch_test.go index 03eff46..4efee02 100644 --- a/rkt/fetch_test.go +++ b/rkt/fetch_test.go @@ -183,9 +183,9 @@ func TestDownloading(t *testing.T) { if tt.hit == true && !ok { t.Fatalf("expected a hit got a miss") } - _, aciFile, err := download(tt.ACIURL, tt.SigURL, ds, nil) + _, aciFile, err := fetch(tt.ACIURL, tt.SigURL, nil, ds, nil) if err != nil { - t.Fatalf("error downloading aci: %v", err) + t.Fatalf("error fetching aci: %v", err) } defer os.Remove(aciFile.Name()) @@ -260,7 +260,7 @@ func TestFetchImage(t *testing.T) { } })) defer ts.Close() - _, err = fetchImage(fmt.Sprintf("%s/app.aci", ts.URL), ds, ks, true) + _, err = fetchImage(fmt.Sprintf("%s/app.aci", ts.URL), "", ds, ks, true) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/rkt/image.go b/rkt/image.go index f8d5c21..ef7cc7e 100644 --- a/rkt/image.go +++ b/rkt/image.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "os" "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types" "github.com/coreos/rkt/cas" @@ -28,7 +27,7 @@ import ( // findImages uses findImage to attain a list of image hashes using discovery if necessary func (al *rktApps) findImages(ds *cas.Store, ks *keystore.Keystore) error { for _, app := range al.apps { - h, err := findImage(app.image, ds, ks, true) + h, err := findImage(app.image, app.asc, ds, ks, true) if err != nil { return err } @@ -40,7 +39,7 @@ func (al *rktApps) findImages(ds *cas.Store, ks *keystore.Keystore) error { // findImage will recognize a ACI hash and use that, import a local file, use // discovery or download an ACI directly. -func findImage(img string, ds *cas.Store, ks *keystore.Keystore, discover bool) (*types.Hash, error) { +func findImage(img string, asc string, ds *cas.Store, ks *keystore.Keystore, discover bool) (*types.Hash, error) { // check if it is a valid hash, if so let it pass through h, err := types.NewHash(img) if err == nil { @@ -56,24 +55,8 @@ func findImage(img string, ds *cas.Store, ks *keystore.Keystore, discover bool) return h, nil } - // import the local file if it exists - file, err := os.Open(img) - if err == nil { - key, err := ds.WriteACI(file, false) - file.Close() - if err != nil { - return nil, fmt.Errorf("%s: %v", img, err) - } - h, err := types.NewHash(key) - if err != nil { - // should never happen - panic(err) - } - return h, nil - } - - // try fetching remotely - key, err := fetchImage(img, ds, ks, discover) + // try fetching the image, potentially remotely + key, err := fetchImage(img, asc, ds, ks, discover) if err != nil { return nil, err } diff --git a/rkt/prepare.go b/rkt/prepare.go index 722550c..7ac5b80 100644 --- a/rkt/prepare.go +++ b/rkt/prepare.go @@ -91,14 +91,13 @@ func runPrepare(args []string) (exit int) { return 1 } - s1img, err := findImage(flagStage1Image, ds, nil, false) + s1img, err := findImage(flagStage1Image, "", ds, nil, false) if err != nil { stderr("prepare: finding stage1 image %q: %v", flagStage1Image, err) return 1 } - ks := getKeystore() - if err := Apps.findImages(ds, ks); err != nil { + if err := Apps.findImages(ds, getKeystore()); err != nil { stderr("%v", err) return 1 } diff --git a/rkt/run.go b/rkt/run.go index 159937f..333b2a1 100644 --- a/rkt/run.go +++ b/rkt/run.go @@ -114,14 +114,13 @@ func runRun(args []string) (exit int) { return 1 } - s1img, err := findImage(flagStage1Image, ds, nil, false) + s1img, err := findImage(flagStage1Image, "", ds, nil, false) if err != nil { stderr("Error finding stage1 image %q: %v", flagStage1Image, err) return 1 } - ks := getKeystore() - if err := Apps.findImages(ds, ks); err != nil { + if err := Apps.findImages(ds, getKeystore()); err != nil { stderr("%v", err) return 1 }