Showing with 96 additions and 3 deletions.
  1. +4 −0 CHANGELOG.md
  2. +7 −3 rkt/image/namefetcher.go
  3. +16 −0 rkt/image/validator.go
  4. +69 −0 tests/rkt_fetch_test.go
@@ -1,5 +1,9 @@
## vUNRELEASED

#### New features and UX changes

- Ensure that the initial name and labels used for discovery match the name and labels in the Image Manifest as specified in the appc spec ([#2311](https://github.com/coreos/rkt/pull/2311)). Users wanting the latest image should use `rkt prepare/run/fetch example.com/aci` without any labels. If the discovery server supports the "latest" pattern, the user can bypass a locally cached image in the store and fetch an updated image using `rkt prepare/run/fetch --no-store example.com/aci` option.

#### Note for packagers

Files generated from sources are no longer checked-in the git repository. Instead, packagers should build them:
@@ -181,7 +181,7 @@ func (f *nameFetcher) fetchVerifiedURL(app *discovery.App, u *url.URL, a *asc) (
}
}

if err := f.validate(appName, aciFile, ascFile); err != nil {
if err := f.validate(app, aciFile, ascFile); err != nil {
return nil, nil, err
}
retAciFile := aciFile
@@ -249,13 +249,17 @@ func (f *nameFetcher) checkIdentity(appName string, ascFile io.ReadSeeker) error
return nil
}

func (f *nameFetcher) validate(appName string, aciFile, ascFile io.ReadSeeker) error {
func (f *nameFetcher) validate(app *discovery.App, aciFile, ascFile io.ReadSeeker) error {
v, err := newValidator(aciFile)
if err != nil {
return err
}

if err := v.ValidateName(appName); err != nil {
if err := v.ValidateName(app.Name.String()); err != nil {
return err
}

if err := v.ValidateLabels(app.Labels); err != nil {
return err
}

@@ -24,6 +24,7 @@ import (

"github.com/appc/spec/aci"
"github.com/appc/spec/schema"
"github.com/appc/spec/schema/types"
"golang.org/x/crypto/openpgp"
pgperrors "golang.org/x/crypto/openpgp/errors"
)
@@ -64,6 +65,21 @@ func (v *validator) ValidateName(imageName string) error {
return nil
}

// ValidateLabels checks if desired image labels are actually the same as
// the ones in the image manifest.
func (v *validator) ValidateLabels(labels map[types.ACIdentifier]string) error {
for n, rv := range labels {
if av, ok := v.manifest.GetLabel(n.String()); ok {
if rv != av {
return fmt.Errorf("requested value for label %q: %q differs from fetched aci label value: %q", n, rv, av)
}
} else {
return fmt.Errorf("requested label %q not provided by the image manifest", n)
}
}
return nil
}

// ValidateWithSignature verifies the image against a given signature
// file.
func (v *validator) ValidateWithSignature(ks *keystore.Keystore, sig io.ReadSeeker) (*openpgp.Entity, error) {
@@ -17,6 +17,7 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@@ -458,3 +459,71 @@ func TestDeferredSignatureDownload(t *testing.T) {
}
}
}

func TestDifferentDiscoveryLabels(t *testing.T) {
manifestTemplate := `{"acKind":"ImageManifest","acVersion":"0.7.4","name":"IMG_NAME","labels":[{"name":"version","value":"1.2.0"},{"name":"arch","value":"amd64"},{"name":"os","value":"linux"}]}`
emptyImage := getEmptyImagePath()
imageName := "localhost/rkt-test-different-discovery-labels-image"
manifest := strings.Replace(manifestTemplate, "IMG_NAME", imageName, -1)
tmpDir := createTempDirOrPanic("rkt-TestDifferentDiscoveryLabels-")
defer os.RemoveAll(tmpDir)

tmpManifest, err := ioutil.TempFile(tmpDir, "manifest")
if err != nil {
panic(fmt.Sprintf("Cannot create temp manifest: %v", err))
}
if err := ioutil.WriteFile(tmpManifest.Name(), []byte(manifest), 0600); err != nil {
panic(fmt.Sprintf("Cannot write to temp manifest: %v", err))
}
defer os.Remove(tmpManifest.Name())

image := patchACI(emptyImage, "rkt-test-different-discovery-labels-image.aci", "--manifest", tmpManifest.Name())
imageFileName := fmt.Sprintf("%s.aci", filepath.Base(imageName))
defer os.Remove(image)

asc := runSignImage(t, image, 1)
defer os.Remove(asc)
ascBase := filepath.Base(asc)

setup := taas.GetDefaultServerSetup()
server := runServer(t, setup)
defer server.Close()
fileSet := make(map[string]string, 2)
fileSet[imageFileName] = image
fileSet[ascBase] = asc
if err := server.UpdateFileSet(fileSet); err != nil {
t.Fatalf("Failed to populate a file list in test aci server: %v", err)
}

tests := []struct {
imageName string
expectedMessage string
}{
{imageName + ":2.0", fmt.Sprintf("requested value for label %q: %q differs from fetched aci label value: %q", "version", "2.0", "1.2.0")},
{imageName + ":latest", fmt.Sprintf("requested value for label %q: %q differs from fetched aci label value: %q", "version", "latest", "1.2.0")},
{imageName + ",arch=armv7b", fmt.Sprintf("requested value for label %q: %q differs from fetched aci label value: %q", "arch", "armv7b", "amd64")},
{imageName + ",unexistinglabel=bla", fmt.Sprintf("requested label %q not provided by the image manifest", "unexistinglabel")},
}

for _, tt := range tests {
testDifferentDiscoveryNameLabels(t, tt.imageName, tt.expectedMessage)
}
}

func testDifferentDiscoveryNameLabels(t *testing.T, imageName string, expectedMessage string) {
ctx := testutils.NewRktRunCtx()
defer ctx.Cleanup()

runRktTrust(t, ctx, "", 1)

// Since aci-server provided meta tag template doesn't contains
// {version} {os} or {arch}, we can just ask for any version/os/arch
// and always get the same ACI
runCmd := fmt.Sprintf("%s --debug --insecure-options=tls fetch %s", ctx.Cmd(), imageName)
child := spawnOrFail(t, runCmd)
defer waitOrFail(t, child, 1)

if err := expectWithOutput(child, expectedMessage); err != nil {
t.Fatalf("Could not find expected msg %q, output follows:\n%v", expectedMessage, err)
}
}