func (ms *ConversionStore) WriteACI(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() cr, err := aci.NewCompressedReader(f) if err != nil { return "", err } defer cr.Close() h := sha512.New() r := io.TeeReader(cr, h) // read the file so we can get the hash if _, err := io.Copy(ioutil.Discard, r); err != nil { return "", fmt.Errorf("error reading ACI: %v", err) } im, err := aci.ManifestFromImage(f) if err != nil { return "", err } key := ms.HashToKey(h) ms.acis[key] = &aciInfo{path: path, key: key, ImageManifest: im} return key, nil }
// newValidator returns a validator instance if passed image is indeed // an ACI image. func newValidator(image io.ReadSeeker) (*validator, error) { manifest, err := aci.ManifestFromImage(image) if err != nil { return nil, err } v := &validator{ image: image, manifest: manifest, } return v, nil }
func (ts *TestStore) WriteACI(path string) (string, error) { data, err := ioutil.ReadFile(path) if err != nil { return "", err } imageID := types.NewHashSHA512(data) rs, err := os.Open(path) if err != nil { return "", err } defer rs.Close() im, err := aci.ManifestFromImage(rs) if err != nil { return "", fmt.Errorf("error retrieving ImageManifest: %v", err) } key := imageID.String() ts.acis[key] = &TestStoreAci{data: data, key: key, ImageManifest: im} return key, nil }
// WriteACI takes an ACI encapsulated in an io.Reader, decompresses it if // necessary, and then stores it in the store under a key based on the image ID // (i.e. the hash of the uncompressed ACI) // latest defines if the aci has to be marked as the latest. For example an ACI // discovered without asking for a specific version (latest pattern). func (s Store) WriteACI(r io.ReadSeeker, latest bool) (string, error) { dr, err := aci.NewCompressedReader(r) if err != nil { return "", fmt.Errorf("error decompressing image: %v", err) } // Write the decompressed image (tar) to a temporary file on disk, and // tee so we can generate the hash h := sha512.New() tr := io.TeeReader(dr, h) fh, err := s.TmpFile() if err != nil { return "", fmt.Errorf("error creating image: %v", err) } if _, err := io.Copy(fh, tr); err != nil { return "", fmt.Errorf("error copying image: %v", err) } im, err := aci.ManifestFromImage(fh) if err != nil { return "", fmt.Errorf("error extracting image manifest: %v", err) } if err := fh.Close(); err != nil { return "", fmt.Errorf("error closing image: %v", err) } // Import the uncompressed image into the store at the real key key := s.HashToKey(h) keyLock, err := lock.ExclusiveKeyLock(s.imageLockDir, key) if err != nil { return "", fmt.Errorf("error locking image: %v", err) } defer keyLock.Close() if err = s.stores[blobType].Import(fh.Name(), key, true); err != nil { return "", fmt.Errorf("error importing image: %v", err) } // Save the imagemanifest using the same key used for the image imj, err := json.Marshal(im) if err != nil { return "", fmt.Errorf("error marshalling image manifest: %v", err) } if err = s.stores[imageManifestType].Write(key, imj); err != nil { return "", fmt.Errorf("error importing image manifest: %v", err) } // Save aciinfo if err = s.db.Do(func(tx *sql.Tx) error { aciinfo := &ACIInfo{ BlobKey: key, AppName: im.Name.String(), ImportTime: time.Now(), Latest: latest, } return WriteACIInfo(tx, aciinfo) }); err != nil { return "", fmt.Errorf("error writing ACI Info: %v", err) } // The treestore for this ACI is not written here as ACIs downloaded as // dependencies of another ACI will be exploded also if never directly used. // Users of treestore should call s.RenderTreeStore before using it. return key, nil }
// fetch opens/downloads and verifies the remote ACI. // If appName is not "", it will be used to check that the manifest contain the correct appName // 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 (f *fetcher) fetch(appName string, aciURL, ascURL string, ascFile *os.File, etag string) (*openpgp.Entity, *os.File, *cacheData, error) { var ( entity *openpgp.Entity cd *cacheData ) u, err := url.Parse(aciURL) if err != nil { return nil, nil, nil, fmt.Errorf("error parsing ACI url: %v", err) } if u.Scheme == "docker" { registryURL := strings.TrimPrefix(aciURL, "docker://") tmpDir, err := f.s.TmpDir() if err != nil { return nil, nil, nil, fmt.Errorf("error creating temporary dir for docker to ACI conversion: %v", err) } indexName := docker2aci.GetIndexName(registryURL) user := "" password := "" if creds, ok := f.dockerAuth[indexName]; ok { user = creds.User password = creds.Password } acis, err := docker2aci.Convert(registryURL, true, tmpDir, tmpDir, user, password) if err != nil { return nil, nil, nil, fmt.Errorf("error converting docker image to ACI: %v", err) } aciFile, err := os.Open(acis[0]) if err != nil { return nil, nil, nil, fmt.Errorf("error opening squashed ACI file: %v", err) } return nil, aciFile, nil, nil } var retrySignature bool if f.ks != nil && ascFile == nil { u, err := url.Parse(ascURL) if err != nil { return nil, nil, nil, fmt.Errorf("error parsing ASC url: %v", err) } if u.Scheme == "file" { ascFile, err = os.Open(u.Path) if err != nil { return nil, nil, nil, fmt.Errorf("error opening signature file: %v", err) } } else { stderr("Downloading signature from %v\n", ascURL) ascFile, err = f.s.TmpFile() if err != nil { return nil, nil, nil, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(ascFile.Name()) err = f.downloadSignatureFile(ascURL, ascFile) switch err { case errStatusAccepted: retrySignature = true stderr("rkt: server requested deferring the signature download") case nil: break default: return nil, nil, nil, fmt.Errorf("error downloading the signature file: %v", err) } } defer ascFile.Close() } // check if the identity used by the signature is in the store before a // possibly expensive download. This is only an optimization and it's // ok to skip the test if the signature will be downloaded later. if !retrySignature && f.ks != nil && appName != "" { if _, err := ascFile.Seek(0, 0); err != nil { return nil, nil, nil, fmt.Errorf("error seeking signature file: %v", err) } if entity, err = f.ks.CheckSignature(appName, bytes.NewReader([]byte{}), ascFile); err != nil { if _, ok := err.(pgperrors.SignatureError); !ok { return nil, nil, nil, err } } } var aciFile *os.File if u.Scheme == "file" { aciFile, err = os.Open(u.Path) if err != nil { return nil, nil, nil, fmt.Errorf("error opening ACI file: %v", err) } } else { aciFile, err = f.s.TmpFile() if err != nil { return nil, aciFile, nil, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(aciFile.Name()) if cd, err = f.downloadACI(aciURL, aciFile, etag); err != nil { return nil, nil, nil, fmt.Errorf("error downloading ACI: %v", err) } if cd.useCached { return nil, nil, cd, nil } } if retrySignature { if err = f.downloadSignatureFile(ascURL, ascFile); err != nil { return nil, aciFile, nil, fmt.Errorf("error downloading the signature file: %v", err) } } manifest, err := aci.ManifestFromImage(aciFile) if err != nil { return nil, aciFile, nil, err } // Check if the downloaded ACI has the correct app name. // The check is only performed when the aci is downloaded through the // discovery protocol, but not with local files or full URL. if appName != "" && manifest.Name.String() != appName { return nil, aciFile, nil, fmt.Errorf("error when reading the app name: %q expected but %q found", appName, manifest.Name.String()) } if f.ks != nil { if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking ACI file: %v", err) } if _, err := ascFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking signature file: %v", err) } if entity, err = f.ks.CheckSignature(manifest.Name.String(), aciFile, ascFile); err != nil { return nil, aciFile, nil, err } } if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking ACI file: %v", err) } return entity, aciFile, cd, nil }
// fetch opens/downloads and verifies the remote ACI. // If appName is not "", it will be used to check that the manifest contain the correct appName // 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 (f *fetcher) fetch(appName string, aciURL, ascURL string, ascFile *os.File, etag string) (*openpgp.Entity, *os.File, *cacheData, error) { var ( entity *openpgp.Entity cd *cacheData ) u, err := url.Parse(aciURL) if err != nil { return nil, nil, nil, fmt.Errorf("error parsing ACI url: %v", err) } if u.Scheme == "docker" { registryURL := strings.TrimPrefix(aciURL, "docker://") storeTmpDir, err := f.s.TmpDir() if err != nil { return nil, nil, nil, fmt.Errorf("error creating temporary dir for docker to ACI conversion: %v", err) } tmpDir, err := ioutil.TempDir(storeTmpDir, "docker2aci-") if err != nil { return nil, nil, nil, err } defer os.RemoveAll(tmpDir) indexName := docker2aci.GetIndexName(registryURL) user := "" password := "" if creds, ok := f.dockerAuth[indexName]; ok { user = creds.User password = creds.Password } acis, err := docker2aci.Convert(registryURL, true, tmpDir, tmpDir, user, password) if err != nil { return nil, nil, nil, fmt.Errorf("error converting docker image to ACI: %v", err) } aciFile, err := os.Open(acis[0]) if err != nil { return nil, nil, nil, fmt.Errorf("error opening squashed ACI file: %v", err) } return nil, aciFile, nil, nil } // attempt to automatically fetch the public key in case it is available on a TLS connection. if globalFlags.TrustKeysFromHttps && !globalFlags.InsecureFlags.SkipTlsCheck() && appName != "" && f.ks != nil { m := &pubkey.Manager{ AuthPerHost: f.headers, InsecureAllowHttp: false, TrustKeysFromHttps: true, Ks: f.ks, Debug: globalFlags.Debug, } pkls, err := m.GetPubKeyLocations(appName) if err != nil { stderr("Error determining key location: %v", err) } else { if err := m.AddKeys(pkls, appName, pubkey.AcceptForce, pubkey.OverrideDeny); err != nil { stderr("Error adding keys: %v", err) } } } var retrySignature bool if f.ks != nil && ascFile == nil { u, err := url.Parse(ascURL) if err != nil { return nil, nil, nil, fmt.Errorf("error parsing ASC url: %v", err) } if u.Scheme == "file" { ascFile, err = os.Open(u.Path) if err != nil { return nil, nil, nil, fmt.Errorf("error opening signature file: %v", err) } } else { stderr("Downloading signature from %v\n", ascURL) ascFile, err = f.s.TmpFile() if err != nil { return nil, nil, nil, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(ascFile.Name()) err = f.downloadSignatureFile(ascURL, ascFile) switch err { case errStatusAccepted: retrySignature = true stderr("rkt: server requested deferring the signature download") case nil: break default: return nil, nil, nil, fmt.Errorf("error downloading the signature file: %v", err) } } defer ascFile.Close() } // check if the identity used by the signature is in the store before a // possibly expensive download. This is only an optimization and it's // ok to skip the test if the signature will be downloaded later. if !retrySignature && f.ks != nil && appName != "" { if _, err := ascFile.Seek(0, 0); err != nil { return nil, nil, nil, fmt.Errorf("error seeking signature file: %v", err) } if entity, err = f.ks.CheckSignature(appName, bytes.NewReader([]byte{}), ascFile); err != nil { if _, ok := err.(pgperrors.SignatureError); !ok { return nil, nil, nil, err } } } var aciFile *os.File if u.Scheme == "file" { aciFile, err = os.Open(u.Path) if err != nil { return nil, nil, nil, fmt.Errorf("error opening ACI file: %v", err) } } else { h := sha512.New() h.Write([]byte(aciURL)) aciURLHash := f.s.HashToKey(h) aciFile, err = f.s.TmpNamedFile(aciURLHash) if err != nil { return nil, aciFile, nil, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(aciFile.Name()) if cd, err = f.downloadACI(aciURL, aciFile, etag); err != nil { return nil, nil, nil, fmt.Errorf("error downloading ACI: %v", err) } if cd.useCached { return nil, nil, cd, nil } } if retrySignature { if err = f.downloadSignatureFile(ascURL, ascFile); err != nil { return nil, aciFile, nil, fmt.Errorf("error downloading the signature file: %v", err) } } manifest, err := aci.ManifestFromImage(aciFile) if err != nil { return nil, aciFile, nil, fmt.Errorf("invalid image manifest: %v", err) } // Check if the downloaded ACI has the correct app name. // The check is only performed when the aci is downloaded through the // discovery protocol, but not with local files or full URL. if appName != "" && manifest.Name.String() != appName { return nil, aciFile, nil, fmt.Errorf("error when reading the app name: %q expected but %q found", appName, manifest.Name.String()) } if f.ks != nil { if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking ACI file: %v", err) } if _, err := ascFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking signature file: %v", err) } if entity, err = f.ks.CheckSignature(manifest.Name.String(), aciFile, ascFile); err != nil { return nil, aciFile, nil, err } } if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, nil, fmt.Errorf("error seeking ACI file: %v", err) } return entity, aciFile, cd, nil }
// 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 (f *fetcher) fetch(aciURL, ascURL string, ascFile *os.File) (*openpgp.Entity, *os.File, error) { var entity *openpgp.Entity u, err := url.Parse(aciURL) if err != nil { return nil, nil, fmt.Errorf("error parsing ACI url: %v", err) } if u.Scheme == "docker" { registryURL := strings.TrimPrefix(aciURL, "docker://") tmpDir, err := f.s.TmpDir() if err != nil { return nil, nil, fmt.Errorf("error creating temporary dir for docker to ACI conversion: %v", err) } indexName := docker2aci.GetIndexName(registryURL) user := "" password := "" if creds, ok := f.dockerAuth[indexName]; ok { user = creds.User password = creds.Password } acis, err := docker2aci.Convert(registryURL, true, tmpDir, tmpDir, user, password) if err != nil { return nil, nil, fmt.Errorf("error converting docker image to ACI: %v", err) } aciFile, err := os.Open(acis[0]) if err != nil { return nil, nil, fmt.Errorf("error opening squashed ACI file: %v", err) } return nil, aciFile, nil } var retrySignature bool if f.ks != nil && ascFile == nil { u, err := url.Parse(ascURL) if err != nil { return nil, nil, fmt.Errorf("error parsing ASC url: %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 { stderr("Downloading signature from %v\n", ascURL) ascFile, err = f.s.TmpFile() if err != nil { return nil, nil, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(ascFile.Name()) err = f.downloadSignatureFile(ascURL, ascFile) switch err { case errStatusAccepted: retrySignature = true stderr("rkt: server requested deferring the signature download") case nil: break default: return nil, nil, fmt.Errorf("error downloading the signature file: %v", err) } } defer ascFile.Close() } 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 = f.s.TmpFile() if err != nil { return nil, aciFile, fmt.Errorf("error setting up temporary file: %v", err) } defer os.Remove(aciFile.Name()) if err = f.downloadACI(aciURL, aciFile); err != nil { return nil, nil, fmt.Errorf("error downloading ACI: %v", err) } } if retrySignature { if err = f.downloadSignatureFile(ascURL, ascFile); err != nil { return nil, aciFile, fmt.Errorf("error downloading the signature file: %v", err) } } if f.ks != nil { manifest, err := aci.ManifestFromImage(aciFile) if err != nil { return nil, aciFile, err } if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, fmt.Errorf("error seeking ACI 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 = f.ks.CheckSignature(manifest.Name.String(), aciFile, ascFile); err != nil { return nil, aciFile, err } } if _, err := aciFile.Seek(0, 0); err != nil { return nil, aciFile, fmt.Errorf("error seeking ACI file: %v", err) } return entity, aciFile, nil }
// WriteACI takes an ACI encapsulated in an io.Reader, decompresses it if // necessary, and then stores it in the store under a key based on the image ID // (i.e. the hash of the uncompressed ACI) // latest defines if the aci has to be marked as the latest. For example an ACI // discovered without asking for a specific version (latest pattern). func (s Store) WriteACI(r io.Reader, latest bool) (string, error) { // Peek at the first 512 bytes of the reader to detect filetype br := bufio.NewReaderSize(r, 32768) hd, err := br.Peek(512) switch err { case nil: case io.EOF: // We may have still peeked enough to guess some types, so fall through default: return "", fmt.Errorf("error reading image header: %v", err) } typ, err := aci.DetectFileType(bytes.NewBuffer(hd)) if err != nil { return "", fmt.Errorf("error detecting image type: %v", err) } dr, err := decompress(br, typ) if err != nil { return "", fmt.Errorf("error decompressing image: %v", err) } // Write the decompressed image (tar) to a temporary file on disk, and // tee so we can generate the hash h := sha512.New() tr := io.TeeReader(dr, h) fh, err := s.TmpFile() if err != nil { return "", fmt.Errorf("error creating image: %v", err) } if _, err := io.Copy(fh, tr); err != nil { return "", fmt.Errorf("error copying image: %v", err) } im, err := aci.ManifestFromImage(fh) if err != nil { return "", fmt.Errorf("error extracting image manifest: %v", err) } if err := fh.Close(); err != nil { return "", fmt.Errorf("error closing image: %v", err) } // Import the uncompressed image into the store at the real key key := s.HashToKey(h) keyLock, err := lock.ExclusiveKeyLock(s.imageLockDir, key) if err != nil { return "", fmt.Errorf("error locking image: %v", err) } defer keyLock.Close() if err = s.stores[blobType].Import(fh.Name(), key, true); err != nil { return "", fmt.Errorf("error importing image: %v", err) } // Save the imagemanifest using the same key used for the image imj, err := json.Marshal(im) if err != nil { return "", fmt.Errorf("error marshalling image manifest: %v", err) } if err = s.stores[imageManifestType].Write(key, imj); err != nil { return "", fmt.Errorf("error importing image manifest: %v", err) } // Save aciinfo if err = s.db.Do(func(tx *sql.Tx) error { aciinfo := &ACIInfo{ BlobKey: key, AppName: im.Name.String(), ImportTime: time.Now(), Latest: latest, } return WriteACIInfo(tx, aciinfo) }); err != nil { return "", fmt.Errorf("error writing ACI Info: %v", err) } // The treestore for this ACI is not written here as ACIs downloaded as // dependencies of another ACI will be exploded also if never directly used. // Users of treestore should call s.RenderTreeStore before using it. return key, nil }