diff --git a/go.mod b/go.mod index a0181fdb02..76c64f213c 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/knqyf263/nested v0.0.1 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 github.com/opencontainers/go-digest v1.0.0-rc1 - github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 // indirect + github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e github.com/sosedoff/gitkit v0.2.0 github.com/stretchr/testify v1.5.1 diff --git a/image/image.go b/image/image.go index 6097d01415..7e913c5861 100644 --- a/image/image.go +++ b/image/image.go @@ -9,12 +9,14 @@ import ( "io/ioutil" "net/http" "os" + "strings" "github.com/aquasecurity/fanal/image/token" + ispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/tarball" @@ -186,7 +188,19 @@ func fileOpener(fileName string) func() (io.ReadCloser, error) { } func tryOCI(fileName string) (v1.Image, error) { - lp, err := layout.FromPath(fileName) + var inputTag, inputFileName string + + // Check if tag is specified in input + if strings.Contains(fileName, ":") { + splitFileName := strings.Split(fileName, ":") + inputFileName = splitFileName[0] + inputTag = splitFileName[1] + } else { + inputFileName = fileName + inputTag = "" + } + + lp, err := layout.FromPath(inputFileName) if err != nil { return nil, xerrors.Errorf("unable to open %s as an OCI Image: %w", fileName, err) } @@ -205,12 +219,38 @@ func tryOCI(fileName string) (v1.Image, error) { return nil, xerrors.New("no valid manifest") } - // Support only first image - h := m.Manifests[0].Digest - img, err := index.Image(h) - if err != nil { - return nil, xerrors.New("invalid OCI image") + // Support image having tag separated by : , otherwise support first image + + if inputTag != "" { + return getOCIImage(m, index, inputTag) + } else { + h := m.Manifests[0].Digest + + img, err := index.Image(h) + if err != nil { + return nil, xerrors.New("invalid OCI image") + } + + return img, nil + } +} + +func getOCIImage(m *v1.IndexManifest, index v1.ImageIndex, inputTag string) (v1.Image, error) { + for _, manifest := range m.Manifests { + annotation := manifest.Annotations + + tag := annotation[ispec.AnnotationRefName] + if tag == inputTag { + h := manifest.Digest + + img, err := index.Image(h) + if err != nil { + return nil, xerrors.New("invalid OCI image") + } + + return img, nil + } } - return img, nil + return nil, xerrors.New("invalid OCI image tag") } diff --git a/image/image_test.go b/image/image_test.go index ddd99dcd1d..a018c20372 100644 --- a/image/image_test.go +++ b/image/image_test.go @@ -226,6 +226,25 @@ func TestNewArchiveImage(t *testing.T) { fileName: "../test/testdata/test.oci", }, }, + { + name: "happy path with OCI Image and tag Format", + args: args{ + fileName: "../test/testdata/test_image_tag.oci:0.0.1", + }, + }, + { + name: "happy path with OCI Image only", + args: args{ + fileName: "../test/testdata/test_image_tag.oci", + }, + }, + { + name: "sad path with OCI Image and invalid tagFormat", + args: args{ + fileName: "../test/testdata/test_image_tag.oci:0.0.0", + }, + wantErr: "invalid OCI image tag", + }, { name: "sad path, oci image not found", args: args{ diff --git a/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab b/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab new file mode 100644 index 0000000000..8967e30737 --- /dev/null +++ b/test/testdata/test_image_tag.oci/blobs/sha256/afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab @@ -0,0 +1 @@ +{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604","size":584},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c","size":2610}]} \ No newline at end of file diff --git a/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 b/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 new file mode 100644 index 0000000000..1156328a5b --- /dev/null +++ b/test/testdata/test_image_tag.oci/blobs/sha256/b23a8f6569ae9ae331226205fa72f480ce5310707d0bc97e611f83fbbbde4604 @@ -0,0 +1 @@ +{"created":"2020-01-03T01:21:37.263809283Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63"]},"history":[{"created":"2020-01-03T01:21:37.132606296Z","created_by":"/bin/sh -c #(nop) COPY file:7bf12aab75c3867a023fe3b8bd6d113d43a4fcc415f3cc27cbcf0fff37b65a02 in / "},{"created":"2020-01-03T01:21:37.263809283Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]} \ No newline at end of file diff --git a/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c b/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c new file mode 100644 index 0000000000..097c1a4338 Binary files /dev/null and b/test/testdata/test_image_tag.oci/blobs/sha256/cdd16bd695eda2819e7637648f573c2ca64896c4f7bff9732ac9db734ca3bc2c differ diff --git a/test/testdata/test_image_tag.oci/index.json b/test/testdata/test_image_tag.oci/index.json new file mode 100644 index 0000000000..1655f80927 --- /dev/null +++ b/test/testdata/test_image_tag.oci/index.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 2, + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:afb744871f99e0ff8e6f253244836ed34c5d805fdb096d3a205ffaf5e9073cab", + "size": 345, + "annotations": { + "org.opencontainers.image.ref.name": "0.0.1" + } + } + ] +} \ No newline at end of file diff --git a/test/testdata/test_image_tag.oci/oci-layout b/test/testdata/test_image_tag.oci/oci-layout new file mode 100644 index 0000000000..21b1439d1c --- /dev/null +++ b/test/testdata/test_image_tag.oci/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion": "1.0.0"} \ No newline at end of file