rkt: move local aci handling into fetchImage()

Also introduces ascFile signature file override and wires up per-app
--signature overrides.

$ rkt run imgs/pauser.aci
 error opening signature file: open /home/coreos/rkt/imgs/pauser.aci.asc: no such file or directory
$

$ rkt --insecure-skip-verify run imgs/pauser.aci
 ^]^]Container rootfs terminated by signal KILL.
$

$ gpg2 --armor --detach-sign imgs/pauser.aci
$ rkt run imgs/pauser.aci
 rkt: signature verified:
   Vito Caputo (CoreOS stuff)
 ^]^]Container rootfs terminated by signal KILL.
$

Non-local aci handling behavior is unchanged other than local-file
--signature overrides apply to remote aci verification as well.
This commit is contained in:
Vito Caputo
2015-04-02 13:11:23 -07:00
parent 12a53931ed
commit 59e34f4feb
5 changed files with 131 additions and 90 deletions
+120 -60
View File
@@ -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 {
+3 -3
View File
@@ -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)
}
+4 -21
View File
@@ -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
}
+2 -3
View File
@@ -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
}
+2 -3
View File
@@ -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
}