Merge pull request #126 from runcom/move-containers-image

*: move to containers/image
This commit is contained in:
Antonio Murdaca
2016-06-27 17:48:58 +02:00
committed by GitHub
91 changed files with 262 additions and 3402 deletions

View File

@@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"github.com/projectatomic/skopeo/image"
"github.com/projectatomic/skopeo/signature"
"github.com/containers/image/image"
"github.com/containers/image/signature"
"github.com/urfave/cli"
)

View File

@@ -1 +0,0 @@
../../../signature/fixtures/corrupt.signature

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../signature/fixtures/image.manifest.json

View File

@@ -0,0 +1,26 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
]
}

View File

@@ -1 +0,0 @@
../../../signature/fixtures/image.signature

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../signature/fixtures/pubring.gpg

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../signature/fixtures/secring.gpg

Binary file not shown.

View File

@@ -1 +0,0 @@
../../../signature/fixtures/trustdb.gpg

Binary file not shown.

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"time"
"github.com/projectatomic/skopeo/docker"
"github.com/projectatomic/skopeo/manifest"
"github.com/containers/image/docker"
"github.com/containers/image/manifest"
"github.com/urfave/cli"
)

View File

@@ -5,7 +5,7 @@ import (
"os"
"github.com/Sirupsen/logrus"
"github.com/projectatomic/skopeo/version"
"github.com/containers/image/version"
"github.com/urfave/cli"
)

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"io/ioutil"
"github.com/projectatomic/skopeo/signature"
"github.com/containers/image/signature"
"github.com/urfave/cli"
)

View File

@@ -5,7 +5,7 @@ import (
"os"
"testing"
"github.com/projectatomic/skopeo/signature"
"github.com/containers/image/signature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

View File

@@ -5,11 +5,11 @@ import (
"fmt"
"strings"
"github.com/projectatomic/skopeo/directory"
"github.com/projectatomic/skopeo/docker"
"github.com/projectatomic/skopeo/image"
"github.com/projectatomic/skopeo/openshift"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/directory"
"github.com/containers/image/docker"
"github.com/containers/image/image"
"github.com/containers/image/openshift"
"github.com/containers/image/types"
"github.com/urfave/cli"
)

24
doc.go
View File

@@ -1,24 +0,0 @@
// Package skopeo provides libraries and commands to interact with containers images.
//
// package main
//
// import (
// "fmt"
//
// "github.com/projectatomic/skopeo/docker"
// )
//
// func main() {
// img, err := docker.NewDockerImage("fedora", "", false)
// if err != nil {
// panic(err)
// }
// b, err := img.Manifest()
// if err != nil {
// panic(err)
// }
// fmt.Printf("%s", string(b))
// }
//
// TODO(runcom)
package skopeo

View File

@@ -6,6 +6,7 @@ rm -rf vendor/
source 'hack/.vendor-helpers.sh'
clone git github.com/urfave/cli v1.17.0
clone git github.com/containers/image master
clone git github.com/Sirupsen/logrus v0.10.0
clone git github.com/go-check/check v1
clone git github.com/stretchr/testify v1.1.3

View File

@@ -1,32 +0,0 @@
package image
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUniqueLayerDigests(t *testing.T) {
for _, test := range []struct{ input, expected []string }{
// Ensure that every element of expected: is unique!
{input: []string{}, expected: []string{}},
{input: []string{"a"}, expected: []string{"a"}},
{input: []string{"a", "b", "c"}, expected: []string{"a", "b", "c"}},
{input: []string{"a", "a", "c"}, expected: []string{"a", "c"}},
{input: []string{"a", "b", "a"}, expected: []string{"a", "b"}},
} {
in := []fsLayersSchema1{}
for _, e := range test.input {
in = append(in, fsLayersSchema1{e})
}
m := manifestSchema1{FSLayers: in}
res := uniqueLayerDigests(&m)
// Test that the length is the same and each expected element is present.
// This requires each element of test.expected to be unique, as noted above.
assert.Len(t, res, len(test.expected))
for _, e := range test.expected {
assert.Contains(t, res, e)
}
}
}

View File

@@ -6,8 +6,8 @@ import (
"os"
"path/filepath"
"github.com/containers/image/manifest"
"github.com/go-check/check"
"github.com/projectatomic/skopeo/manifest"
)
func init() {

View File

@@ -1,5 +0,0 @@
{
"schemaVersion": 99999,
"name": "mitr/noversion-nonsense",
"tag": "latest"
}

View File

@@ -1,56 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
"size": 2094,
"digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
"size": 1922,
"digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd",
"platform": {
"architecture": "amd64",
"os": "linux",
"features": [
"sse"
]
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
"size": 2084,
"digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2",
"platform": {
"architecture": "s390x",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
"size": 2084,
"digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "armv7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
"size": 2090,
"digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "armv8"
}
}
]
}

View File

@@ -1,11 +0,0 @@
{
"schemaVersion": 1,
"name": "mitr/buxybox",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
],
"history": [
],
"signatures": 1
}

View File

@@ -1,44 +0,0 @@
{
"schemaVersion": 1,
"name": "mitr/buxybox",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"f1b5eb0a1215f663765d509b6cdf3841bc2bcff0922346abb943d1342d469a97\",\"parent\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"created\":\"2016-03-03T11:29:44.222098366Z\",\"container\":\"c0924f5b281a1992127d0afc065e59548ded8880b08aea4debd56d4497acb17a\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Checksum=4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\"],\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"parent\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:38.563048924Z\",\"container\":\"fd4cf54dcd239fbae9bdade9db48e41880b436d27cb5313f60952a46ab04deff\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Name=atomic-test-2\"],\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:32.948089874Z\",\"container\":\"56f0fe1dfc95755dd6cda10f7215c9937a8d9c6348d079c581a261fd4c2f3a5f\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) MAINTAINER \\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OZ45:U3IG:TDOI:PMBD:NGP2:LDIW:II2U:PSBI:MMCZ:YZUP:TUUO:XPZT",
"kty": "EC",
"x": "ReC5c0J9tgXSdUL4_xzEt5RsD8kFt2wWSgJcpAcOQx8",
"y": "3sBGEqQ3ZMeqPKwQBAadN2toOUEASha18xa0WwsDF-M"
},
"alg": "ES256"
},
"signature": "dV1paJ3Ck1Ph4FcEhg_frjqxdlGdI6-ywRamk6CvMOcaOEUdCWCpCPQeBQpD2N6tGjkoG1BbstkFNflllfenCw",
"protected": "eyJmb3JtYXRMZW5ndGgiOjU0NzgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wNC0xOFQyMDo1NDo0MloifQ"
}
]
}

View File

@@ -1,26 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
]
}

View File

@@ -1,10 +0,0 @@
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
]
}

View File

@@ -1,8 +0,0 @@
package manifest
const (
// TestV2S2ManifestDigest is the Docker manifest digest of "v2s2.manifest.json"
TestDockerV2S2ManifestDigest = "sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"
// TestV2S1ManifestDigest is the Docker manifest digest of "v2s1.manifest.json"
TestDockerV2S1ManifestDigest = "sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1"
)

View File

@@ -1,100 +0,0 @@
package manifest
import (
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGuessMIMEType(t *testing.T) {
cases := []struct {
path string
mimeType string
}{
{"v2s2.manifest.json", DockerV2Schema2MIMEType},
{"v2list.manifest.json", DockerV2ListMIMEType},
{"v2s1.manifest.json", DockerV2Schema1MIMEType},
{"v2s1-invalid-signatures.manifest.json", DockerV2Schema1MIMEType},
{"v2s2nomime.manifest.json", DockerV2Schema2MIMEType}, // It is unclear whether this one is legal, but we should guess v2s2 if anything at all.
{"unknown-version.manifest.json", ""},
{"non-json.manifest.json", ""}, // Not a manifest (nor JSON) at all
}
for _, c := range cases {
manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
require.NoError(t, err)
mimeType := GuessMIMEType(manifest)
assert.Equal(t, c.mimeType, mimeType)
}
}
func TestDigest(t *testing.T) {
cases := []struct {
path string
digest string
}{
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest},
{"v2s1.manifest.json", TestDockerV2S1ManifestDigest},
}
for _, c := range cases {
manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
require.NoError(t, err)
digest, err := Digest(manifest)
require.NoError(t, err)
assert.Equal(t, c.digest, digest)
}
manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
digest, err := Digest(manifest)
assert.Error(t, err)
digest, err = Digest([]byte{})
require.NoError(t, err)
assert.Equal(t, "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", digest)
}
func TestMatchesDigest(t *testing.T) {
cases := []struct {
path string
digest string
result bool
}{
// Success
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest, true},
{"v2s1.manifest.json", TestDockerV2S1ManifestDigest, true},
// No match (switched s1/s2)
{"v2s2.manifest.json", TestDockerV2S1ManifestDigest, false},
{"v2s1.manifest.json", TestDockerV2S2ManifestDigest, false},
// Unrecognized algorithm
{"v2s2.manifest.json", "md5:2872f31c5c1f62a694fbd20c1e85257c", false},
// Mangled format
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest + "abc", false},
{"v2s2.manifest.json", TestDockerV2S2ManifestDigest[:20], false},
{"v2s2.manifest.json", "", false},
}
for _, c := range cases {
manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path))
require.NoError(t, err)
res, err := MatchesDigest(manifest, c.digest)
require.NoError(t, err)
assert.Equal(t, c.result, res)
}
manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
// Even a correct SHA256 hash is rejected if we can't strip the JSON signature.
hash := sha256.Sum256(manifest)
res, err := MatchesDigest(manifest, "sha256:"+hex.EncodeToString(hash[:]))
assert.False(t, res)
assert.Error(t, err)
res, err = MatchesDigest([]byte{}, "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
assert.True(t, res)
assert.NoError(t, err)
}

View File

@@ -1,84 +0,0 @@
package signature
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSignDockerManifest(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
manifest, err := ioutil.ReadFile("fixtures/image.manifest.json")
require.NoError(t, err)
// Successful signing
signature, err := SignDockerManifest(manifest, TestImageSignatureReference, mech, TestKeyFingerprint)
require.NoError(t, err)
verified, err := VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint)
assert.NoError(t, err)
assert.Equal(t, TestImageSignatureReference, verified.DockerReference)
assert.Equal(t, TestImageManifestDigest, verified.DockerManifestDigest)
// Error computing Docker manifest
invalidManifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
_, err = SignDockerManifest(invalidManifest, TestImageSignatureReference, mech, TestKeyFingerprint)
assert.Error(t, err)
// Error creating blob to sign
_, err = SignDockerManifest(manifest, "", mech, TestKeyFingerprint)
assert.Error(t, err)
// Error signing
_, err = SignDockerManifest(manifest, TestImageSignatureReference, mech, "this fingerprint doesn't exist")
assert.Error(t, err)
}
func TestVerifyDockerManifestSignature(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
manifest, err := ioutil.ReadFile("fixtures/image.manifest.json")
require.NoError(t, err)
signature, err := ioutil.ReadFile("fixtures/image.signature")
require.NoError(t, err)
// Successful verification
sig, err := VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint)
require.NoError(t, err)
assert.Equal(t, TestImageSignatureReference, sig.DockerReference)
assert.Equal(t, TestImageManifestDigest, sig.DockerManifestDigest)
// For extra paranoia, test that we return nil data on error.
// Error computing Docker manifest
invalidManifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
sig, err = VerifyDockerManifestSignature(signature, invalidManifest, TestImageSignatureReference, mech, TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)
// Error verifying signature
corruptSignature, err := ioutil.ReadFile("fixtures/corrupt.signature")
sig, err = VerifyDockerManifestSignature(corruptSignature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)
// Key fingerprint mismatch
sig, err = VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, "unexpected fingerprint")
assert.Error(t, err)
assert.Nil(t, sig)
// Docker reference mismatch
sig, err = VerifyDockerManifestSignature(signature, manifest, "example.com/doesnt/match", mech, TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)
// Docker manifest digest mismatch
sig, err = VerifyDockerManifestSignature(signature, []byte("unexpected manifest"), TestImageSignatureReference, mech, TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)
}

View File

@@ -1,4 +0,0 @@
/*.gpg~
/.gpg-v21-migrated
/private-keys-v1.d
/random_seed

Binary file not shown.

View File

@@ -1 +0,0 @@
../v2s1-invalid-signatures.manifest.json

View File

@@ -1 +0,0 @@
../dir-img-valid/signature-1

View File

@@ -1 +0,0 @@
../dir-img-valid/manifest.json

View File

@@ -1 +0,0 @@
../invalid-blob.signature

View File

@@ -1 +0,0 @@
../dir-img-valid/signature-1

View File

@@ -1,27 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
],
"extra": "this manifest has been modified"
}

View File

@@ -1 +0,0 @@
../dir-img-valid/signature-1

View File

@@ -1 +0,0 @@
../dir-img-valid/signature-1

View File

@@ -1 +0,0 @@
../dir-img-valid/manifest.json

View File

@@ -1 +0,0 @@
../dir-img-valid/manifest.json

View File

@@ -1 +0,0 @@
../dir-img-valid/signature-1

View File

@@ -1 +0,0 @@
../image.manifest.json

Binary file not shown.

Binary file not shown.

View File

@@ -1,26 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
]
}

Binary file not shown.

View File

@@ -1,84 +0,0 @@
{
"default": [
{
"type": "reject"
}
],
"specific": {
"example.com/playground": [
{
"type": "insecureAcceptAnything"
}
],
"example.com/production": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/keys/employee-gpg-keyring"
}
],
"example.com/hardened": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/keys/employee-gpg-keyring",
"signedIdentity": {
"type": "matchRepository"
}
},
{
"type": "signedBy",
"keyType": "signedByGPGKeys",
"keyPath": "/keys/public-key-signing-gpg-keyring",
"signedIdentity": {
"type": "matchExact"
}
},
{
"type": "signedBaseLayer",
"baseLayerIdentity": {
"type": "exactRepository",
"dockerRepository": "registry.access.redhat.com/rhel7/rhel"
}
}
],
"example.com/hardened-x509": [
{
"type": "signedBy",
"keyType": "X509Certificates",
"keyPath": "/keys/employee-cert-file",
"signedIdentity": {
"type": "matchRepository"
}
},
{
"type": "signedBy",
"keyType": "signedByX509CAs",
"keyPath": "/keys/public-key-signing-ca-file"
}
],
"registry.access.redhat.com": [
{
"type": "signedBy",
"keyType": "signedByGPGKeys",
"keyPath": "/keys/RH-key-signing-key-gpg-keyring"
}
],
"bogus/key-data-example": [
{
"type": "signedBy",
"keyType": "signedByGPGKeys",
"keyData": "bm9uc2Vuc2U="
}
],
"bogus/signed-identity-example": [
{
"type": "signedBaseLayer",
"baseLayerIdentity": {
"type": "exactReference",
"dockerReference": "registry.access.redhat.com/rhel7/rhel:latest"
}
}
]
}
}

View File

@@ -1,19 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
mI0EVurzqQEEAL3qkFq4K2URtSWVDYnQUNA9HdM9sqS2eAWfqUFMrkD5f+oN+LBL
tPyaE5GNLA0vXY7nHAM2TeM8ijZ/eMP17Raj64JL8GhCymL3wn2jNvb9XaF0R0s6
H0IaRPPu45A3SnxLwm4Orc/9Z7/UxtYjKSg9xOaTiVPzJgaf5Vm4J4ApABEBAAG0
EnNrb3BlbyB0ZXN0aW5nIGtleYi4BBMBAgAiBQJW6vOpAhsDBgsJCAcDAgYVCAIJ
CgsEFgIDAQIeAQIXgAAKCRDbcvIYi7RsyBbOBACgJFiKDlQ1UyvsNmGqJ7D0OpbS
1OppJlradKgZXyfahFswhFI+7ZREvELLHbinq3dBy5cLXRWzQKdJZNHknSN5Tjf2
0ipVBQuqpcBo+dnKiG4zH6fhTri7yeTZksIDfsqlI6FXDOdKLUSnahagEBn4yU+x
jHPvZk5SuuZv56A45biNBFbq86kBBADIC/9CsAlOmRALuYUmkhcqEjuFwn3wKz2d
IBjzgvro7zcVNNCgxQfMEjcUsvEh5cx13G3QQHcwOKy3M6Bv6VMhfZjd+1P1el4P
0fJS8GFmhWRBknMN8jFsgyohQeouQ798RFFv94KszfStNnr/ae8oao5URmoUXSCa
/MdUxn0YKwARAQABiJ8EGAECAAkFAlbq86kCGwwACgkQ23LyGIu0bMjUywQAq0dn
lUpDNSoLTcpNWuVvHQ7c/qmnE4TyiSLiRiAywdEWA6gMiyhUUucuGsEhMFP1WX1k
UNwArZ6UG7BDOUsvngP7jKGNqyUOQrq1s/r8D+0MrJGOWErGLlfttO2WeoijECkI
5qm8cXzAra3Xf/Z3VjxYTKSnNu37LtZkakdTdYE=
=tJAt
-----END PGP PUBLIC KEY BLOCK-----

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,11 +0,0 @@
{
"schemaVersion": 1,
"name": "mitr/buxybox",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
],
"history": [
],
"signatures": 1
}

View File

@@ -1,10 +0,0 @@
package signature
const (
// TestImageManifestDigest is the Docker manifest digest of "image.manifest.json"
TestImageManifestDigest = "sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"
// TestImageSignatureReference is the Docker image reference signed in "image.signature"
TestImageSignatureReference = "testing/manifest"
// TestKeyFingerprint is the fingerprint of the private key in this directory.
TestKeyFingerprint = "1D8230F6CDB6A06716E414C1DB72F2188BB46CC8"
)

View File

@@ -1,149 +0,0 @@
package signature
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mSI map[string]interface{} // To minimize typing the long name
// A short-hand way to get a JSON object field value or panic. No error handling done, we know
// what we are working with, a panic in a test is good enough, and fitting test cases on a single line
// is a priority.
func x(m mSI, fields ...string) mSI {
for _, field := range fields {
// Not .(mSI) because type assertion of an unnamed type to a named type always fails (the types
// are not "identical"), but the assignment is fine because they are "assignable".
m = m[field].(map[string]interface{})
}
return m
}
func TestValidateExactMapKeys(t *testing.T) {
// Empty map and keys
err := validateExactMapKeys(mSI{})
assert.NoError(t, err)
// Success
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "b", "a")
assert.NoError(t, err)
// Extra map keys
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "a")
assert.Error(t, err)
// Extra expected keys
err = validateExactMapKeys(mSI{"a": 1}, "b", "a")
assert.Error(t, err)
// Unexpected key values
err = validateExactMapKeys(mSI{"a": 1}, "b")
assert.Error(t, err)
}
func TestMapField(t *testing.T) {
// Field not found
_, err := mapField(mSI{"a": mSI{}}, "b")
assert.Error(t, err)
// Field has a wrong type
_, err = mapField(mSI{"a": 1}, "a")
assert.Error(t, err)
// Success
// FIXME? We can't use mSI as the type of child, that type apparently can't be converted to the raw map type.
child := map[string]interface{}{"b": mSI{}}
m, err := mapField(mSI{"a": child, "b": nil}, "a")
require.NoError(t, err)
assert.Equal(t, child, m)
}
func TestStringField(t *testing.T) {
// Field not found
_, err := stringField(mSI{"a": "x"}, "b")
assert.Error(t, err)
// Field has a wrong type
_, err = stringField(mSI{"a": 1}, "a")
assert.Error(t, err)
// Success
s, err := stringField(mSI{"a": "x", "b": nil}, "a")
require.NoError(t, err)
assert.Equal(t, "x", s)
}
// implementsUnmarshalJSON is a minimalistic type used to detect that
// paranoidUnmarshalJSONObject uses the json.Unmarshaler interface of resolved
// pointers.
type implementsUnmarshalJSON bool
// Compile-time check that Policy implements json.Unmarshaler.
var _ json.Unmarshaler = (*implementsUnmarshalJSON)(nil)
func (dest *implementsUnmarshalJSON) UnmarshalJSON(data []byte) error {
_ = data // We don't care, not really.
*dest = true // Mark handler as called
return nil
}
func TestParanoidUnmarshalJSONObject(t *testing.T) {
type testStruct struct {
A string
B int
}
ts := testStruct{}
var unmarshalJSONCalled implementsUnmarshalJSON
tsResolver := func(key string) interface{} {
switch key {
case "a":
return &ts.A
case "b":
return &ts.B
case "implementsUnmarshalJSON":
return &unmarshalJSONCalled
default:
return nil
}
}
// Empty object
ts = testStruct{}
err := paranoidUnmarshalJSONObject([]byte(`{}`), tsResolver)
require.NoError(t, err)
assert.Equal(t, testStruct{}, ts)
// Success
ts = testStruct{}
err = paranoidUnmarshalJSONObject([]byte(`{"a":"x", "b":2}`), tsResolver)
require.NoError(t, err)
assert.Equal(t, testStruct{A: "x", B: 2}, ts)
// json.Unamarshaler is used for decoding values
ts = testStruct{}
unmarshalJSONCalled = implementsUnmarshalJSON(false)
err = paranoidUnmarshalJSONObject([]byte(`{"implementsUnmarshalJSON":true}`), tsResolver)
require.NoError(t, err)
assert.Equal(t, unmarshalJSONCalled, implementsUnmarshalJSON(true))
// Various kinds of invalid input
for _, input := range []string{
``, // Empty input
`&`, // Entirely invalid JSON
`1`, // Not an object
`{&}`, // Invalid key JSON
`{1:1}`, // Key not a string
`{"b":1, "b":1}`, // Duplicate key
`{"thisdoesnotexist":1}`, // Key rejected by resolver
`{"a":&}`, // Invalid value JSON
`{"a":1}`, // Type mismatch
`{"a":"value"}{}`, // Extra data after object
} {
ts = testStruct{}
err := paranoidUnmarshalJSONObject([]byte(input), tsResolver)
assert.Error(t, err, input)
}
}

View File

@@ -1,149 +0,0 @@
package signature
import (
"bytes"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testGPGHomeDirectory = "./fixtures"
)
func TestNewGPGSigningMechanism(t *testing.T) {
// A dumb test just for code coverage. We test more with newGPGSigningMechanismInDirectory().
_, err := NewGPGSigningMechanism()
assert.NoError(t, err)
}
func TestNewGPGSigningMechanismInDirectory(t *testing.T) {
// A dumb test just for code coverage.
_, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
assert.NoError(t, err)
// The various GPG failure cases are not obviously easy to reach.
}
func TestGPGSigningMechanismImportKeysFromBytes(t *testing.T) {
testDir, err := ioutil.TempDir("", "gpg-import-keys")
require.NoError(t, err)
defer os.RemoveAll(testDir)
mech, err := newGPGSigningMechanismInDirectory(testDir)
require.NoError(t, err)
// Try validating a signature when the key is unknown.
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
content, signingFingerprint, err := mech.Verify(signature)
require.Error(t, err)
// Successful import
keyBlob, err := ioutil.ReadFile("./fixtures/public-key.gpg")
require.NoError(t, err)
keyIdentities, err := mech.ImportKeysFromBytes(keyBlob)
require.NoError(t, err)
assert.Equal(t, []string{TestKeyFingerprint}, keyIdentities)
// After import, the signature should validate.
content, signingFingerprint, err = mech.Verify(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This is not JSON\n"), content)
assert.Equal(t, TestKeyFingerprint, signingFingerprint)
// Two keys: just concatenate the valid input twice.
keyIdentities, err = mech.ImportKeysFromBytes(bytes.Join([][]byte{keyBlob, keyBlob}, nil))
require.NoError(t, err)
assert.Equal(t, []string{TestKeyFingerprint, TestKeyFingerprint}, keyIdentities)
// Invalid input: This is accepted anyway by GPG, just returns no keys.
keyIdentities, err = mech.ImportKeysFromBytes([]byte("This is invalid"))
require.NoError(t, err)
assert.Equal(t, []string{}, keyIdentities)
// The various GPG/GPGME failures cases are not obviously easy to reach.
}
func TestGPGSigningMechanismSign(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
// Successful signing
content := []byte("content")
signature, err := mech.Sign(content, TestKeyFingerprint)
require.NoError(t, err)
signedContent, signingFingerprint, err := mech.Verify(signature)
require.NoError(t, err)
assert.EqualValues(t, content, signedContent)
assert.Equal(t, TestKeyFingerprint, signingFingerprint)
// Error signing
_, err = mech.Sign(content, "this fingerprint doesn't exist")
assert.Error(t, err)
// The various GPG/GPGME failures cases are not obviously easy to reach.
}
func assertSigningError(t *testing.T, content []byte, fingerprint string, err error) {
assert.Error(t, err)
assert.Nil(t, content)
assert.Empty(t, fingerprint)
}
func TestGPGSigningMechanismVerify(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
// Successful verification
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
content, signingFingerprint, err := mech.Verify(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This is not JSON\n"), content)
assert.Equal(t, TestKeyFingerprint, signingFingerprint)
// For extra paranoia, test that we return nil data on error.
// Completely invalid signature.
content, signingFingerprint, err = mech.Verify([]byte{})
assertSigningError(t, content, signingFingerprint, err)
content, signingFingerprint, err = mech.Verify([]byte("invalid signature"))
assertSigningError(t, content, signingFingerprint, err)
// Literal packet, not a signature
signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
// Encrypted data, not a signature.
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
// FIXME? Is there a way to create a multi-signature so that gpgme_op_verify returns multiple signatures?
// Expired signature
signature, err = ioutil.ReadFile("./fixtures/expired.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
// Corrupt signature
signature, err = ioutil.ReadFile("./fixtures/corrupt.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
// Valid signature with an unknown key
signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
// The various GPG/GPGME failures cases are not obviously easy to reach.
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
package signature
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPRSignedBaseLayerIsSignatureAuthorAccepted(t *testing.T) {
pr, err := NewPRSignedBaseLayer(NewPRMMatchRepository())
require.NoError(t, err)
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil)
assertSARUnknown(t, sar, parsedSig, err)
}
func TestPRSignedBaseLayerIsRunningImageAllowed(t *testing.T) {
// This will obviously need to change after signedBaseLayer is implemented.
pr, err := NewPRSignedBaseLayer(NewPRMMatchRepository())
require.NoError(t, err)
// Pass a nil pointer to, kind of, test that the return value does not depend on the image.
res, err := pr.isRunningImageAllowed(nil)
assertRunningRejectedPolicyRequirement(t, res, err)
}

View File

@@ -1,239 +0,0 @@
package signature
import (
"io/ioutil"
"os"
"path"
"testing"
"github.com/projectatomic/skopeo/directory"
"github.com/projectatomic/skopeo/image"
"github.com/projectatomic/skopeo/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// dirImageMock returns a types.Image for a directory, claiming a specified intendedDockerReference.
func dirImageMock(dir, intendedDockerReference string) types.Image {
return image.FromSource(&dirImageSourceMock{
ImageSource: directory.NewDirImageSource(dir),
intendedDockerReference: intendedDockerReference,
})
}
// dirImageSourceMock inherits dirImageSource, but overrides its IntendedDockerReference method.
type dirImageSourceMock struct {
types.ImageSource
intendedDockerReference string
}
func (d *dirImageSourceMock) IntendedDockerReference() string {
return d.intendedDockerReference
}
func TestPRSignedByIsSignatureAuthorAccepted(t *testing.T) {
ktGPG := SBKeyTypeGPGKeys
prm := NewPRMMatchExact()
testImage := dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
testImageSig, err := ioutil.ReadFile("fixtures/dir-img-valid/signature-1")
require.NoError(t, err)
// Successful validation, with KeyData and KeyPath
pr, err := NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sar, parsedSig, err := pr.isSignatureAuthorAccepted(testImage, testImageSig)
assertSARAccepted(t, sar, parsedSig, err, Signature{
DockerManifestDigest: TestImageManifestDigest,
DockerReference: "testing/manifest:latest",
})
keyData, err := ioutil.ReadFile("fixtures/public-key.gpg")
require.NoError(t, err)
pr, err = NewPRSignedByKeyData(ktGPG, keyData, prm)
require.NoError(t, err)
sar, parsedSig, err = pr.isSignatureAuthorAccepted(testImage, testImageSig)
assertSARAccepted(t, sar, parsedSig, err, Signature{
DockerManifestDigest: TestImageManifestDigest,
DockerReference: "testing/manifest:latest",
})
// Unimplemented and invalid KeyType values
for _, keyType := range []sbKeyType{SBKeyTypeSignedByGPGKeys,
SBKeyTypeX509Certificates,
SBKeyTypeSignedByX509CAs,
sbKeyType("This is invalid"),
} {
// Do not use NewPRSignedByKeyData, because it would reject invalid values.
pr := &prSignedBy{
KeyType: keyType,
KeyData: []byte("abc"),
SignedIdentity: prm,
}
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil)
assertSARRejected(t, sar, parsedSig, err)
}
// Both KeyPath and KeyData set. Do not use NewPRSignedBy*, because it would reject this.
prSB := &prSignedBy{
KeyType: ktGPG,
KeyPath: "/foo/bar",
KeyData: []byte("abc"),
SignedIdentity: prm,
}
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err = prSB.isSignatureAuthorAccepted(nil, nil)
assertSARRejected(t, sar, parsedSig, err)
// Invalid KeyPath
pr, err = NewPRSignedByKeyPath(ktGPG, "/this/does/not/exist", prm)
require.NoError(t, err)
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, nil)
assertSARRejected(t, sar, parsedSig, err)
// Errors initializing the temporary GPG directory and mechanism are not obviously easy to reach.
// KeyData has no public keys.
pr, err = NewPRSignedByKeyData(ktGPG, []byte{}, prm)
require.NoError(t, err)
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, nil)
assertSARRejectedPolicyRequirement(t, sar, parsedSig, err)
// A signature which does not GPG verify
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
// Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater..
sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, []byte("invalid signature"))
assertSARRejected(t, sar, parsedSig, err)
// A valid signature using an unknown key.
// (This is (currently?) rejected through the "mech.Verify fails" path, not the "!identityFound" path,
// because we use a temporary directory and only import the trusted keys.)
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sig, err := ioutil.ReadFile("fixtures/unknown-key.signature")
require.NoError(t, err)
// Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater..
sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, sig)
assertSARRejected(t, sar, parsedSig, err)
// A valid signature of an invalid JSON.
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sig, err = ioutil.ReadFile("fixtures/invalid-blob.signature")
require.NoError(t, err)
// Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater..
sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, sig)
assertSARRejected(t, sar, parsedSig, err)
assert.IsType(t, InvalidSignatureError{}, err)
// A valid signature with a rejected identity.
nonmatchingPRM, err := NewPRMExactReference("this/doesnt:match")
require.NoError(t, err)
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", nonmatchingPRM)
require.NoError(t, err)
sar, parsedSig, err = pr.isSignatureAuthorAccepted(testImage, testImageSig)
assertSARRejectedPolicyRequirement(t, sar, parsedSig, err)
// Error reading image manifest
image := dirImageMock("fixtures/dir-img-no-manifest", "testing/manifest:latest")
sig, err = ioutil.ReadFile("fixtures/dir-img-no-manifest/signature-1")
require.NoError(t, err)
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig)
assertSARRejected(t, sar, parsedSig, err)
// Error computing manifest digest
image = dirImageMock("fixtures/dir-img-manifest-digest-error", "testing/manifest:latest")
sig, err = ioutil.ReadFile("fixtures/dir-img-manifest-digest-error/signature-1")
require.NoError(t, err)
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig)
assertSARRejected(t, sar, parsedSig, err)
// A valid signature with a non-matching manifest
image = dirImageMock("fixtures/dir-img-modified-manifest", "testing/manifest:latest")
sig, err = ioutil.ReadFile("fixtures/dir-img-modified-manifest/signature-1")
require.NoError(t, err)
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig)
assertSARRejectedPolicyRequirement(t, sar, parsedSig, err)
}
// createInvalidSigDir creates a directory suitable for dirImageMock, in which image.Signatures()
// fails.
// The caller should eventually call os.RemoveAll on the returned path.
func createInvalidSigDir(t *testing.T) string {
dir, err := ioutil.TempDir("", "skopeo-test-unreadable-signature")
require.NoError(t, err)
err = ioutil.WriteFile(path.Join(dir, "manifest.json"), []byte("{}"), 0644)
require.NoError(t, err)
// Creating a 000-permissions file would work for unprivileged accounts, but root (in particular,
// in the Docker container we use for testing) would still have access. So, create a symlink
// pointing to itself, to cause an ELOOP. (Note that a symlink pointing to a nonexistent file would be treated
// just like a nonexistent signature file, and not an error.)
err = os.Symlink("signature-1", path.Join(dir, "signature-1"))
require.NoError(t, err)
return dir
}
func TestPRSignedByIsRunningImageAllowed(t *testing.T) {
ktGPG := SBKeyTypeGPGKeys
prm := NewPRMMatchExact()
// A simple success case: single valid signature.
image := dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
pr, err := NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err := pr.isRunningImageAllowed(image)
assertRunningAllowed(t, allowed, err)
// Error reading signatures
invalidSigDir := createInvalidSigDir(t)
defer os.RemoveAll(invalidSigDir)
image = dirImageMock(invalidSigDir, "testing/manifest:latest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningRejected(t, allowed, err)
// No signatures
image = dirImageMock("fixtures/dir-img-unsigned", "testing/manifest:latest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, allowed, err)
// 1 invalid signature: use dir-img-valid, but a non-matching Docker reference
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:notlatest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, allowed, err)
// 2 valid signatures
image = dirImageMock("fixtures/dir-img-valid-2", "testing/manifest:latest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningAllowed(t, allowed, err)
// One invalid, one valid signature (in this order)
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:latest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningAllowed(t, allowed, err)
// 2 invalid signatures: use dir-img-valid-2, but a non-matching Docker reference
image = dirImageMock("fixtures/dir-img-valid-2", "testing/manifest:notlatest")
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
require.NoError(t, err)
allowed, err = pr.isRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, allowed, err)
}

View File

@@ -1,32 +0,0 @@
package signature
import "testing"
func TestPRInsecureAcceptAnythingIsSignatureAuthorAccepted(t *testing.T) {
pr := NewPRInsecureAcceptAnything()
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil)
assertSARUnknown(t, sar, parsedSig, err)
}
func TestPRInsecureAcceptAnythingIsRunningImageAllowed(t *testing.T) {
pr := NewPRInsecureAcceptAnything()
// Pass a nil pointer to, kind of, test that the return value does not depend on the image.
res, err := pr.isRunningImageAllowed(nil)
assertRunningAllowed(t, res, err)
}
func TestPRRejectIsSignatureAuthorAccepted(t *testing.T) {
pr := NewPRReject()
// Pass nil pointers to, kind of, test that the return value does not depend on the parameters.
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil)
assertSARRejectedPolicyRequirement(t, sar, parsedSig, err)
}
func TestPRRejectIsRunningImageAllowed(t *testing.T) {
// This will obviously need to change after this is implemented.
pr := NewPRReject()
// Pass a nil pointer to, kind of, test that the return value does not depend on the image.
res, err := pr.isRunningImageAllowed(nil)
assertRunningRejectedPolicyRequirement(t, res, err)
}

View File

@@ -1,504 +0,0 @@
package signature
import (
"fmt"
"os"
"testing"
"github.com/projectatomic/skopeo/reference"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPolicyRequirementError(t *testing.T) {
// A stupid test just to keep code coverage
s := "test"
err := PolicyRequirementError(s)
assert.Equal(t, s, err.Error())
}
func TestPolicyContextChangeState(t *testing.T) {
pc, err := NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}})
require.NoError(t, err)
defer pc.Destroy()
require.Equal(t, pcReady, pc.state)
err = pc.changeState(pcReady, pcInUse)
require.NoError(t, err)
err = pc.changeState(pcReady, pcInUse)
require.Error(t, err)
// Return state to pcReady to allow pc.Destroy to clean up.
err = pc.changeState(pcInUse, pcReady)
require.NoError(t, err)
}
func TestPolicyContextNewDestroy(t *testing.T) {
pc, err := NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}})
require.NoError(t, err)
assert.Equal(t, pcReady, pc.state)
err = pc.Destroy()
require.NoError(t, err)
assert.Equal(t, pcDestroyed, pc.state)
// Trying to destroy when not pcReady
pc, err = NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}})
require.NoError(t, err)
err = pc.changeState(pcReady, pcInUse)
require.NoError(t, err)
err = pc.Destroy()
require.Error(t, err)
assert.Equal(t, pcInUse, pc.state) // The state, and hopefully nothing else, has changed.
err = pc.changeState(pcInUse, pcReady)
require.NoError(t, err)
err = pc.Destroy()
assert.NoError(t, err)
}
func TestFullyExpandedDockerReference(t *testing.T) {
sha256Digest := "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
// Test both that fullyExpandedDockerReference returns the expected value (fullName+suffix),
// and that .FullName returns the expected value (fullName), i.e. that the two functions are
// consistent.
for inputName, fullName := range map[string]string{
"example.com/ns/repo": "example.com/ns/repo",
"example.com/repo": "example.com/repo",
"localhost/ns/repo": "localhost/ns/repo",
// Note that "localhost" is special here: notlocalhost/repo is be parsed as docker.io/notlocalhost.repo:
"localhost/repo": "localhost/repo",
"notlocalhost/repo": "docker.io/notlocalhost/repo",
"docker.io/ns/repo": "docker.io/ns/repo",
"docker.io/library/repo": "docker.io/library/repo",
"docker.io/repo": "docker.io/library/repo",
"ns/repo": "docker.io/ns/repo",
"library/repo": "docker.io/library/repo",
"repo": "docker.io/library/repo",
} {
for inputSuffix, mappedSuffix := range map[string]string{
":tag": ":tag",
sha256Digest: sha256Digest,
"": "",
// A github.com/distribution/reference value can have a tag and a digest at the same time!
// github.com/skopeo/reference handles that by dropping the tag. That is not obviously the
// right thing to do, but it is at least reasonable, so test that we keep behaving reasonably.
// This test case should not be construed to make this an API promise.
":tag" + sha256Digest: sha256Digest,
} {
fullInput := inputName + inputSuffix
ref, err := reference.ParseNamed(fullInput)
require.NoError(t, err)
assert.Equal(t, fullName, ref.FullName(), fullInput)
expanded, err := fullyExpandedDockerReference(ref)
require.NoError(t, err)
assert.Equal(t, fullName+mappedSuffix, expanded, fullInput)
}
}
}
func TestPolicyContextRequirementsForImage(t *testing.T) {
ktGPG := SBKeyTypeGPGKeys
prm := NewPRMMatchExact()
policy := &Policy{
Default: PolicyRequirements{NewPRReject()},
Specific: map[string]PolicyRequirements{},
}
// Just put _something_ into the Specific map for the keys we care about, and make it pairwise
// distinct so that we can compare the values and show them when debugging the tests.
for _, scope := range []string{
"unmatched",
"hostname.com",
"hostname.com/namespace",
"hostname.com/namespace/repo",
"hostname.com/namespace/repo:latest",
"hostname.com/namespace/repo:tag2",
"localhost",
"localhost/namespace",
"localhost/namespace/repo",
"localhost/namespace/repo:latest",
"localhost/namespace/repo:tag2",
"deep.com",
"deep.com/n1",
"deep.com/n1/n2",
"deep.com/n1/n2/n3",
"deep.com/n1/n2/n3/repo",
"deep.com/n1/n2/n3/repo:tag2",
"docker.io",
"docker.io/library",
"docker.io/library/busybox",
"docker.io/namespaceindocker",
"docker.io/namespaceindocker/repo",
"docker.io/namespaceindocker/repo:tag2",
// Note: these non-fully-expanded repository names are not matched against canonical (shortened)
// Docker names; they are instead parsed as starting with hostnames.
"busybox",
"library/busybox",
"namespaceindocker",
"namespaceindocker/repo",
"namespaceindocker/repo:tag2",
} {
policy.Specific[scope] = PolicyRequirements{xNewPRSignedByKeyData(ktGPG, []byte(scope), prm)}
}
pc, err := NewPolicyContext(policy)
require.NoError(t, err)
for input, matched := range map[string]string{
// Full match
"hostname.com/namespace/repo:latest": "hostname.com/namespace/repo:latest",
"hostname.com/namespace/repo:tag2": "hostname.com/namespace/repo:tag2",
"hostname.com/namespace/repo": "hostname.com/namespace/repo:latest",
"localhost/namespace/repo:latest": "localhost/namespace/repo:latest",
"localhost/namespace/repo:tag2": "localhost/namespace/repo:tag2",
"localhost/namespace/repo": "localhost/namespace/repo:latest",
"deep.com/n1/n2/n3/repo:tag2": "deep.com/n1/n2/n3/repo:tag2",
// Repository match
"hostname.com/namespace/repo:notlatest": "hostname.com/namespace/repo",
"localhost/namespace/repo:notlatest": "localhost/namespace/repo",
"deep.com/n1/n2/n3/repo:nottag2": "deep.com/n1/n2/n3/repo",
// Namespace match
"hostname.com/namespace/notrepo:latest": "hostname.com/namespace",
"localhost/namespace/notrepo:latest": "localhost/namespace",
"deep.com/n1/n2/n3/notrepo:tag2": "deep.com/n1/n2/n3",
"deep.com/n1/n2/notn3/repo:tag2": "deep.com/n1/n2",
"deep.com/n1/notn2/n3/repo:tag2": "deep.com/n1",
// Host name match
"hostname.com/notnamespace/repo:latest": "hostname.com",
"localhost/notnamespace/repo:latest": "localhost",
"deep.com/notn1/n2/n3/repo:tag2": "deep.com",
// Default
"this.doesnt/match:anything": "",
"this.doesnt/match-anything/defaulttag": "",
// docker.io canonizalication effects
"docker.io/library/busybox": "docker.io/library/busybox",
"library/busybox": "docker.io/library/busybox",
"busybox": "docker.io/library/busybox",
"docker.io/library/somethinginlibrary": "docker.io/library",
"library/somethinginlibrary": "docker.io/library",
"somethinginlibrary": "docker.io/library",
"docker.io/namespaceindocker/repo:tag2": "docker.io/namespaceindocker/repo:tag2",
"namespaceindocker/repo:tag2": "docker.io/namespaceindocker/repo:tag2",
"docker.io/namespaceindocker/repo:nottag2": "docker.io/namespaceindocker/repo",
"namespaceindocker/repo:nottag2": "docker.io/namespaceindocker/repo",
"docker.io/namespaceindocker/notrepo:tag2": "docker.io/namespaceindocker",
"namespaceindocker/notrepo:tag2": "docker.io/namespaceindocker",
"docker.io/notnamespaceindocker/repo:tag2": "docker.io",
"notnamespaceindocker/repo:tag2": "docker.io",
} {
var expected PolicyRequirements
if matched != "" {
e, ok := policy.Specific[matched]
require.True(t, ok, fmt.Sprintf("case %s: expected reqs not found", input))
expected = e
} else {
expected = policy.Default
}
reqs, err := pc.requirementsForImage(refImageMock(input))
require.NoError(t, err)
comment := fmt.Sprintf("case %s: %#v", input, reqs[0])
// Do not sue assert.Equal, which would do a deep contents comparison; we want to compare
// the pointers. Also, == does not work on slices; so test that the slices start at the
// same element and have the same length.
assert.True(t, &(reqs[0]) == &(expected[0]), comment)
assert.True(t, len(reqs) == len(expected), comment)
}
// Invalid reference format
_, err = pc.requirementsForImage(refImageMock("UPPERCASEISINVALID"))
assert.Error(t, err)
}
func TestPolicyContextGetSignaturesWithAcceptedAuthor(t *testing.T) {
expectedSig := &Signature{
DockerManifestDigest: TestImageManifestDigest,
DockerReference: "testing/manifest:latest",
}
pc, err := NewPolicyContext(&Policy{
Default: PolicyRequirements{NewPRReject()},
Specific: map[string]PolicyRequirements{
"docker.io/testing/manifest:latest": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchExact()),
},
"docker.io/testing/manifest:twoAccepts": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
},
"docker.io/testing/manifest:acceptReject": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
NewPRReject(),
},
"docker.io/testing/manifest:acceptUnknown": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
xNewPRSignedBaseLayer(NewPRMMatchRepository()),
},
"docker.io/testing/manifest:rejectUnknown": {
NewPRReject(),
xNewPRSignedBaseLayer(NewPRMMatchRepository()),
},
"docker.io/testing/manifest:unknown": {
xNewPRSignedBaseLayer(NewPRMMatchRepository()),
},
"docker.io/testing/manifest:unknown2": {
NewPRInsecureAcceptAnything(),
},
"docker.io/testing/manifest:invalidEmptyRequirements": {},
},
})
require.NoError(t, err)
defer pc.Destroy()
// Success
image := dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
sigs, err := pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Equal(t, []*Signature{expectedSig}, sigs)
// Two signatures
// FIXME? Use really different signatures for this?
image = dirImageMock("fixtures/dir-img-valid-2", "testing/manifest:latest")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Equal(t, []*Signature{expectedSig, expectedSig}, sigs)
// No signatures
image = dirImageMock("fixtures/dir-img-unsigned", "testing/manifest:latest")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// Only invalid signatures
image = dirImageMock("fixtures/dir-img-modified-manifest", "testing/manifest:latest")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// 1 invalid, 1 valid signature (in this order)
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:latest")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Equal(t, []*Signature{expectedSig}, sigs)
// Two sarAccepted results for one signature
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:twoAccepts")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Equal(t, []*Signature{expectedSig}, sigs)
// sarAccepted+sarRejected for a signature
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:acceptReject")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// sarAccepted+sarUnknown for a signature
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:acceptUnknown")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Equal(t, []*Signature{expectedSig}, sigs)
// sarRejected+sarUnknown for a signature
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:rejectUnknown")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// sarUnknown only
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:unknown")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:unknown2")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// Empty list of requirements (invalid)
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
require.NoError(t, err)
assert.Empty(t, sigs)
// Failures: Make sure we return nil sigs.
// Unexpected state (context already destroyed)
destroyedPC, err := NewPolicyContext(pc.Policy)
require.NoError(t, err)
err = destroyedPC.Destroy()
require.NoError(t, err)
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
sigs, err = destroyedPC.GetSignaturesWithAcceptedAuthor(image)
assert.Error(t, err)
assert.Nil(t, sigs)
// Not testing the pcInUse->pcReady transition, that would require custom PolicyRequirement
// implementations meddling with the state, or threads. This is for catching trivial programmer
// mistakes only, anyway.
// Invalid IntendedDockerReference value
image = dirImageMock("fixtures/dir-img-valid", "UPPERCASEISINVALID")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
assert.Error(t, err)
assert.Nil(t, sigs)
// Error reading signatures.
invalidSigDir := createInvalidSigDir(t)
defer os.RemoveAll(invalidSigDir)
image = dirImageMock(invalidSigDir, "testing/manifest:latest")
sigs, err = pc.GetSignaturesWithAcceptedAuthor(image)
assert.Error(t, err)
assert.Nil(t, sigs)
}
func TestPolicyContextIsRunningImageAllowed(t *testing.T) {
pc, err := NewPolicyContext(&Policy{
Default: PolicyRequirements{NewPRReject()},
Specific: map[string]PolicyRequirements{
"docker.io/testing/manifest:latest": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchExact()),
},
"docker.io/testing/manifest:twoAllows": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
},
"docker.io/testing/manifest:allowDeny": {
xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()),
NewPRReject(),
},
"docker.io/testing/manifest:reject": {
NewPRReject(),
},
"docker.io/testing/manifest:acceptAnything": {
NewPRInsecureAcceptAnything(),
},
"docker.io/testing/manifest:invalidEmptyRequirements": {},
},
})
require.NoError(t, err)
defer pc.Destroy()
// Success
image := dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
res, err := pc.IsRunningImageAllowed(image)
assertRunningAllowed(t, res, err)
// Two signatures
// FIXME? Use really different signatures for this?
image = dirImageMock("fixtures/dir-img-valid-2", "testing/manifest:latest")
res, err = pc.IsRunningImageAllowed(image)
assertRunningAllowed(t, res, err)
// No signatures
image = dirImageMock("fixtures/dir-img-unsigned", "testing/manifest:latest")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, res, err)
// Only invalid signatures
image = dirImageMock("fixtures/dir-img-modified-manifest", "testing/manifest:latest")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, res, err)
// 1 invalid, 1 valid signature (in this order)
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:latest")
res, err = pc.IsRunningImageAllowed(image)
assertRunningAllowed(t, res, err)
// Two allowed results
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:twoAllows")
res, err = pc.IsRunningImageAllowed(image)
assertRunningAllowed(t, res, err)
// Allow + deny results
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:allowDeny")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, res, err)
// prReject works
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:reject")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, res, err)
// prInsecureAcceptAnything works
image = dirImageMock("fixtures/dir-img-mixed", "testing/manifest:acceptAnything")
res, err = pc.IsRunningImageAllowed(image)
assertRunningAllowed(t, res, err)
// Empty list of requirements (invalid)
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejectedPolicyRequirement(t, res, err)
// Unexpected state (context already destroyed)
destroyedPC, err := NewPolicyContext(pc.Policy)
require.NoError(t, err)
err = destroyedPC.Destroy()
require.NoError(t, err)
image = dirImageMock("fixtures/dir-img-valid", "testing/manifest:latest")
res, err = destroyedPC.IsRunningImageAllowed(image)
assertRunningRejected(t, res, err)
// Not testing the pcInUse->pcReady transition, that would require custom PolicyRequirement
// implementations meddling with the state, or threads. This is for catching trivial programmer
// mistakes only, anyway.
// Invalid IntendedDockerReference value
image = dirImageMock("fixtures/dir-img-valid", "UPPERCASEISINVALID")
res, err = pc.IsRunningImageAllowed(image)
assertRunningRejected(t, res, err)
}
// Helpers for validating PolicyRequirement.isSignatureAuthorAccepted results:
// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarRejected result
// with the expected signature.
func assertSARAccepted(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error, expectedSig Signature) {
assert.Equal(t, sarAccepted, sar)
assert.Equal(t, &expectedSig, parsedSig)
assert.NoError(t, err)
}
// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarRejected result.
func assertSARRejected(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) {
assert.Equal(t, sarRejected, sar)
assert.Nil(t, parsedSig)
assert.Error(t, err)
}
// assertSARRejectedPolicyRequiremnt verifies that isSignatureAuthorAccepted returns a consistent sarRejected resul,
// and that the returned error is a PolicyRequirementError..
func assertSARRejectedPolicyRequirement(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) {
assertSARRejected(t, sar, parsedSig, err)
assert.IsType(t, PolicyRequirementError(""), err)
}
// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarUnknown result.
func assertSARUnknown(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) {
assert.Equal(t, sarUnknown, sar)
assert.Nil(t, parsedSig)
assert.NoError(t, err)
}
// Helpers for validating PolicyRequirement.isRunningImageAllowed results:
// assertRunningAllowed verifies that isRunningImageAllowed returns a consistent true result
func assertRunningAllowed(t *testing.T, allowed bool, err error) {
assert.Equal(t, true, allowed)
assert.NoError(t, err)
}
// assertRunningRejected verifies that isRunningImageAllowed returns a consistent false result
func assertRunningRejected(t *testing.T, allowed bool, err error) {
assert.Equal(t, false, allowed)
assert.Error(t, err)
}
// assertRunningRejectedPolicyRequirement verifies that isRunningImageAllowed returns a consistent false result
// and that the returned error is a PolicyRequirementError.
func assertRunningRejectedPolicyRequirement(t *testing.T, allowed bool, err error) {
assertRunningRejected(t, allowed, err)
assert.IsType(t, PolicyRequirementError(""), err)
}

View File

@@ -1,208 +0,0 @@
package signature
import (
"fmt"
"testing"
"github.com/projectatomic/skopeo/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
fullRHELRef = "registry.access.redhat.com/rhel7/rhel:7.2.3"
untaggedRHELRef = "registry.access.redhat.com/rhel7/rhel"
)
func TestParseDockerReferences(t *testing.T) {
const (
ok1 = "busybox"
ok2 = fullRHELRef
bad1 = "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES"
bad2 = ""
)
// Success
r1, r2, err := parseDockerReferences(ok1, ok2)
require.NoError(t, err)
assert.Equal(t, ok1, r1.String())
assert.Equal(t, ok2, r2.String())
// Failures
for _, refs := range [][]string{
{bad1, ok2},
{ok1, bad2},
{bad1, bad2},
} {
_, _, err := parseDockerReferences(refs[0], refs[1])
assert.Error(t, err)
}
}
// refImageMock is a mock of types.Image which returns itself in IntendedDockerReference.
type refImageMock string
func (ref refImageMock) IntendedDockerReference() string {
return string(ref)
}
func (ref refImageMock) Manifest() ([]byte, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) ManifestMatchesDigest(expectedDigest string) (bool, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) Signatures() ([][]byte, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) LayerDigests() ([]string, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) LayersCommand(layers ...string) error {
panic("unexpected call to a mock function")
}
func (ref refImageMock) Inspect() (*types.ImageInspectInfo, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) DockerTar() ([]byte, error) {
panic("unexpected call to a mock function")
}
func (ref refImageMock) GetRepositoryTags() ([]string, error) {
panic("unexpected call to a mock function")
}
type prmTableTest struct {
imageRef, sigRef string
result bool
}
// Test cases for exact reference match
var prmExactMatchTestTable = []prmTableTest{
// Success, simple matches
{"busybox:latest", "busybox:latest", true},
{fullRHELRef, fullRHELRef, true},
// Non-canonical reference format is canonicalized
{"library/busybox:latest", "busybox:latest", true},
{"busybox:latest", "library/busybox:latest", true},
{"docker.io/library/busybox:latest", "busybox:latest", true},
{"busybox:latest", "docker.io/library/busybox:latest", true},
// Mismatch
{"busybox:latest", "busybox:notlatest", false},
{"busybox:latest", "notbusybox:latest", false},
{"busybox:latest", "hostname/library/busybox:notlatest", false},
{"hostname/library/busybox:latest", "busybox:notlatest", false},
{"busybox:latest", fullRHELRef, false},
// Missing tags
{"busybox", "busybox:latest", false},
{"busybox:latest", "busybox", false},
{"busybox", "busybox", false},
// Invalid format
{"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false},
{"busybox:latest", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false},
{"", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false},
// Even if they are exactly equal, invalid values are rejected.
{"INVALID", "INVALID", false},
}
// Test cases for repository-only reference match
var prmRepositoryMatchTestTable = []prmTableTest{
// Success, simple matches
{"busybox:latest", "busybox:latest", true},
{fullRHELRef, fullRHELRef, true},
// Non-canonical reference format is canonicalized
{"library/busybox:latest", "busybox:latest", true},
{"busybox:latest", "library/busybox:latest", true},
{"docker.io/library/busybox:latest", "busybox:latest", true},
{"busybox:latest", "docker.io/library/busybox:latest", true},
// The same as above, but with mismatching tags
{"busybox:latest", "busybox:notlatest", true},
{fullRHELRef + "tagsuffix", fullRHELRef, true},
{"library/busybox:latest", "busybox:notlatest", true},
{"busybox:latest", "library/busybox:notlatest", true},
{"docker.io/library/busybox:notlatest", "busybox:latest", true},
{"busybox:notlatest", "docker.io/library/busybox:latest", true},
// The same as above, but with defaulted tags (should not actually happen)
{"busybox", "busybox:notlatest", true},
{fullRHELRef, untaggedRHELRef, true},
{"library/busybox", "busybox", true},
{"busybox", "library/busybox", true},
{"docker.io/library/busybox", "busybox", true},
{"busybox", "docker.io/library/busybox", true},
// Mismatch
{"busybox:latest", "notbusybox:latest", false},
{"hostname/library/busybox:latest", "busybox:notlatest", false},
{"busybox:latest", fullRHELRef, false},
// Invalid format
{"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false},
{"busybox:latest", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false},
{"", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false},
// Even if they are exactly equal, invalid values are rejected.
{"INVALID", "INVALID", false},
}
func TestPRMMatchExactMatchesDockerReference(t *testing.T) {
prm := NewPRMMatchExact()
for _, test := range prmExactMatchTestTable {
res := prm.matchesDockerReference(refImageMock(test.imageRef), test.sigRef)
assert.Equal(t, test.result, res, fmt.Sprintf("%s vs. %s", test.imageRef, test.sigRef))
}
}
func TestPRMMatchRepositoryMatchesDockerReference(t *testing.T) {
prm := NewPRMMatchRepository()
for _, test := range prmRepositoryMatchTestTable {
res := prm.matchesDockerReference(refImageMock(test.imageRef), test.sigRef)
assert.Equal(t, test.result, res, fmt.Sprintf("%s vs. %s", test.imageRef, test.sigRef))
}
}
// forbiddenImageMock is a mock of types.Image which ensures IntendedDockerReference is not called
type forbiddenImageMock string
func (ref forbiddenImageMock) IntendedDockerReference() string {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Manifest() ([]byte, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) ManifestMatchesDigest(expectedDigest string) (bool, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Signatures() ([][]byte, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) LayerDigests() ([]string, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) LayersCommand(layers ...string) error {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) Inspect() (*types.ImageInspectInfo, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) DockerTar() ([]byte, error) {
panic("unexpected call to a mock function")
}
func (ref forbiddenImageMock) GetRepositoryTags() ([]string, error) {
panic("unexpected call to a mock function")
}
func TestPRMExactReferenceMatchesDockerReference(t *testing.T) {
for _, test := range prmExactMatchTestTable {
// Do not use NewPRMExactReference, we want to also test the case with an invalid DockerReference,
// even though NewPRMExactReference should never let it happen.
prm := prmExactReference{DockerReference: test.imageRef}
res := prm.matchesDockerReference(forbiddenImageMock(""), test.sigRef)
assert.Equal(t, test.result, res, fmt.Sprintf("%s vs. %s", test.imageRef, test.sigRef))
}
}
func TestPRMExactRepositoryMatchesDockerReference(t *testing.T) {
for _, test := range prmRepositoryMatchTestTable {
// Do not use NewPRMExactRepository, we want to also test the case with an invalid DockerReference,
// even though NewPRMExactRepository should never let it happen.
prm := prmExactRepository{DockerRepository: test.imageRef}
res := prm.matchesDockerReference(forbiddenImageMock(""), test.sigRef)
assert.Equal(t, test.result, res, fmt.Sprintf("%s vs. %s", test.imageRef, test.sigRef))
}
}

View File

@@ -1,288 +0,0 @@
package signature
import (
"encoding/json"
"fmt"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvalidSignatureError(t *testing.T) {
// A stupid test just to keep code coverage
s := "test"
err := InvalidSignatureError{msg: s}
assert.Equal(t, s, err.Error())
}
func TestMarshalJSON(t *testing.T) {
// Empty string values
s := privateSignature{Signature{DockerManifestDigest: "", DockerReference: "_"}}
_, err := s.MarshalJSON()
assert.Error(t, err)
s = privateSignature{Signature{DockerManifestDigest: "_", DockerReference: ""}}
_, err = s.MarshalJSON()
assert.Error(t, err)
// Success
s = privateSignature{Signature{DockerManifestDigest: "digest!@#", DockerReference: "reference#@!"}}
marshaled, err := s.marshalJSONWithVariables(0, "CREATOR")
require.NoError(t, err)
assert.Equal(t, []byte("{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{\"creator\":\"CREATOR\",\"timestamp\":0}}"),
marshaled)
// We can't test MarshalJSON directly because the timestamp will keep changing, so just test that
// it doesn't fail. And call it through the JSON package for a good measure.
_, err = json.Marshal(s)
assert.NoError(t, err)
}
// Return the result of modifying validJSON with fn and unmarshaling it into *sig
func tryUnmarshalModifiedSignature(t *testing.T, sig *privateSignature, validJSON []byte, modifyFn func(mSI)) error {
var tmp mSI
err := json.Unmarshal(validJSON, &tmp)
require.NoError(t, err)
modifyFn(tmp)
testJSON, err := json.Marshal(tmp)
require.NoError(t, err)
*sig = privateSignature{}
return json.Unmarshal(testJSON, sig)
}
func TestUnmarshalJSON(t *testing.T) {
var s privateSignature
// Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our
// UnmarshalJSON implementation; so test that first, then test our error handling for completeness.
err := json.Unmarshal([]byte("&"), &s)
assert.Error(t, err)
err = s.UnmarshalJSON([]byte("&"))
assert.Error(t, err)
// Not an object
err = json.Unmarshal([]byte("1"), &s)
assert.Error(t, err)
// Start with a valid JSON.
validSig := privateSignature{
Signature{
DockerManifestDigest: "digest!@#",
DockerReference: "reference#@!",
},
}
validJSON, err := validSig.MarshalJSON()
require.NoError(t, err)
// Success
s = privateSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
assert.Equal(t, validSig, s)
// Various ways to corrupt the JSON
breakFns := []func(mSI){
// A top-level field is missing
func(v mSI) { delete(v, "critical") },
func(v mSI) { delete(v, "optional") },
// Extra top-level sub-object
func(v mSI) { v["unexpected"] = 1 },
// "critical" not an object
func(v mSI) { v["critical"] = 1 },
// "optional" not an object
func(v mSI) { v["optional"] = 1 },
// A field of "critical" is missing
func(v mSI) { delete(x(v, "critical"), "type") },
func(v mSI) { delete(x(v, "critical"), "image") },
func(v mSI) { delete(x(v, "critical"), "identity") },
// Extra field of "critical"
func(v mSI) { x(v, "critical")["unexpected"] = 1 },
// Invalid "type"
func(v mSI) { x(v, "critical")["type"] = 1 },
func(v mSI) { x(v, "critical")["type"] = "unexpected" },
// Invalid "image" object
func(v mSI) { x(v, "critical")["image"] = 1 },
func(v mSI) { delete(x(v, "critical", "image"), "docker-manifest-digest") },
func(v mSI) { x(v, "critical", "image")["unexpected"] = 1 },
// Invalid "docker-manifest-digest"
func(v mSI) { x(v, "critical", "image")["docker-manifest-digest"] = 1 },
// Invalid "identity" object
func(v mSI) { x(v, "critical")["identity"] = 1 },
func(v mSI) { delete(x(v, "critical", "identity"), "docker-reference") },
func(v mSI) { x(v, "critical", "identity")["unexpected"] = 1 },
// Invalid "docker-reference"
func(v mSI) { x(v, "critical", "identity")["docker-reference"] = 1 },
}
for _, fn := range breakFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
assert.Error(t, err)
}
// Modifications to "optional" are allowed and ignored
allowedModificationFns := []func(mSI){
// Add an optional field
func(v mSI) { x(v, "optional")["unexpected"] = 1 },
// Delete an optional field
func(v mSI) { delete(x(v, "optional"), "creator") },
}
for _, fn := range allowedModificationFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
require.NoError(t, err)
assert.Equal(t, validSig, s)
}
}
func TestSign(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
sig := privateSignature{
Signature{
DockerManifestDigest: "digest!@#",
DockerReference: "reference#@!",
},
}
// Successful signing
signature, err := sig.sign(mech, TestKeyFingerprint)
require.NoError(t, err)
verified, err := verifyAndExtractSignature(mech, signature, signatureAcceptanceRules{
validateKeyIdentity: func(keyIdentity string) error {
if keyIdentity != TestKeyFingerprint {
return fmt.Errorf("Unexpected keyIdentity")
}
return nil
},
validateSignedDockerReference: func(signedDockerReference string) error {
if signedDockerReference != sig.DockerReference {
return fmt.Errorf("Unexpected signedDockerReference")
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
if signedDockerManifestDigest != sig.DockerManifestDigest {
return fmt.Errorf("Unexpected signedDockerManifestDigest")
}
return nil
},
})
require.NoError(t, err)
assert.Equal(t, sig.Signature, *verified)
// Error creating blob to sign
_, err = privateSignature{}.sign(mech, TestKeyFingerprint)
assert.Error(t, err)
// Error signing
_, err = sig.sign(mech, "this fingerprint doesn't exist")
assert.Error(t, err)
}
func TestVerifyAndExtractSignature(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
type triple struct{ keyIdentity, signedDockerReference, signedDockerManifestDigest string }
var wanted, recorded triple
// recordingRules are a plausible signatureAcceptanceRules implementations, but equally
// importantly record that we are passing the correct values to the rule callbacks.
recordingRules := signatureAcceptanceRules{
validateKeyIdentity: func(keyIdentity string) error {
recorded.keyIdentity = keyIdentity
if keyIdentity != wanted.keyIdentity {
return fmt.Errorf("keyIdentity mismatch")
}
return nil
},
validateSignedDockerReference: func(signedDockerReference string) error {
recorded.signedDockerReference = signedDockerReference
if signedDockerReference != wanted.signedDockerReference {
return fmt.Errorf("signedDockerReference mismatch")
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
recorded.signedDockerManifestDigest = signedDockerManifestDigest
if signedDockerManifestDigest != wanted.signedDockerManifestDigest {
return fmt.Errorf("signedDockerManifestDigest mismatch")
}
return nil
},
}
signature, err := ioutil.ReadFile("./fixtures/image.signature")
require.NoError(t, err)
signatureData := triple{
keyIdentity: TestKeyFingerprint,
signedDockerReference: TestImageSignatureReference,
signedDockerManifestDigest: TestImageManifestDigest,
}
// Successful verification
wanted = signatureData
recorded = triple{}
sig, err := verifyAndExtractSignature(mech, signature, recordingRules)
require.NoError(t, err)
assert.Equal(t, TestImageSignatureReference, sig.DockerReference)
assert.Equal(t, TestImageManifestDigest, sig.DockerManifestDigest)
assert.Equal(t, signatureData, recorded)
// For extra paranoia, test that we return a nil signature object on error.
// Completely invalid signature.
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, []byte{}, recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, triple{}, recorded)
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, []byte("invalid signature"), recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, triple{}, recorded)
// Valid signature of non-JSON: asked for keyIdentity, only
invalidBlobSignature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, invalidBlobSignature, recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, triple{keyIdentity: signatureData.keyIdentity}, recorded)
// Valid signature with a wrong key: asked for keyIdentity, only
wanted = signatureData
wanted.keyIdentity = "unexpected fingerprint"
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, signature, recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, triple{keyIdentity: signatureData.keyIdentity}, recorded)
// Valid signature with a wrong manifest digest: asked for keyIdentity and signedDockerManifestDigest
wanted = signatureData
wanted.signedDockerManifestDigest = "invalid digest"
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, signature, recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, triple{
keyIdentity: signatureData.keyIdentity,
signedDockerManifestDigest: signatureData.signedDockerManifestDigest,
}, recorded)
// Valid signature with a wrong image reference
wanted = signatureData
wanted.signedDockerReference = "unexpected docker reference"
recorded = triple{}
sig, err = verifyAndExtractSignature(mech, signature, recordingRules)
assert.Error(t, err)
assert.Nil(t, sig)
assert.Equal(t, signatureData, recorded)
}

189
vendor/github.com/containers/image/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,189 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/types"
)
// manifestPath returns a path for the manifest within a directory using our conventions.

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"net/http"
"github.com/projectatomic/skopeo/image"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/image"
"github.com/containers/image/types"
)
// Image is a Docker-specific implementation of types.Image with a few extra methods

View File

@@ -8,9 +8,9 @@ import (
"net/http"
"github.com/Sirupsen/logrus"
"github.com/projectatomic/skopeo/manifest"
"github.com/projectatomic/skopeo/reference"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/manifest"
"github.com/containers/image/reference"
"github.com/containers/image/types"
)
type dockerImageDestination struct {

View File

@@ -8,9 +8,9 @@ import (
"strconv"
"github.com/Sirupsen/logrus"
"github.com/projectatomic/skopeo/manifest"
"github.com/projectatomic/skopeo/reference"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/manifest"
"github.com/containers/image/reference"
"github.com/containers/image/types"
)
type errFetchManifest struct {

View File

@@ -1,6 +1,6 @@
package docker
import "github.com/projectatomic/skopeo/reference"
import "github.com/containers/image/reference"
// parseDockerImageName converts a string into a reference and tag value.
func parseDockerImageName(img string) (reference.Named, string, error) {

View File

@@ -12,9 +12,9 @@ import (
"strings"
"time"
"github.com/projectatomic/skopeo/directory"
"github.com/projectatomic/skopeo/manifest"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/directory"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
)
var (

View File

@@ -13,10 +13,10 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/projectatomic/skopeo/docker"
"github.com/projectatomic/skopeo/manifest"
"github.com/projectatomic/skopeo/types"
"github.com/projectatomic/skopeo/version"
"github.com/containers/image/docker"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/image/version"
)
// openshiftClient is configuration for dealing with a single image stream, for reading or writing.

View File

@@ -5,7 +5,7 @@ package signature
import (
"fmt"
"github.com/projectatomic/skopeo/manifest"
"github.com/containers/image/manifest"
)
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
@@ -48,7 +48,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
return err
}
if !matches {
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %s does not match", signedDockerManifestDigest, signedDockerManifestDigest)}
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest)}
}
return nil
},

View File

@@ -18,7 +18,7 @@ import (
"fmt"
"io/ioutil"
"github.com/projectatomic/skopeo/reference"
"github.com/containers/image/reference"
)
// InvalidPolicyFormatError is returned when parsing an invalid policy configuration.

View File

@@ -11,8 +11,8 @@ import (
"github.com/Sirupsen/logrus"
distreference "github.com/docker/distribution/reference"
"github.com/projectatomic/skopeo/reference"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/reference"
"github.com/containers/image/types"
)
// PolicyRequirementError is an explanatory text for rejecting a signature or an image.

View File

@@ -4,7 +4,7 @@ package signature
import (
"github.com/Sirupsen/logrus"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/types"
)
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {

View File

@@ -9,8 +9,8 @@ import (
"os"
"strings"
"github.com/projectatomic/skopeo/manifest"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
)
func (pr *prSignedBy) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {

View File

@@ -2,7 +2,7 @@
package signature
import "github.com/projectatomic/skopeo/types"
import "github.com/containers/image/types"
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {
// prInsecureAcceptAnything semantics: Every image is allowed to run,

View File

@@ -3,8 +3,8 @@
package signature
import (
"github.com/projectatomic/skopeo/reference"
"github.com/projectatomic/skopeo/types"
"github.com/containers/image/reference"
"github.com/containers/image/types"
)
// parseDockerReferences converts two reference strings into parsed entities, failing on any error

View File

@@ -8,7 +8,7 @@ import (
"fmt"
"time"
"github.com/projectatomic/skopeo/version"
"github.com/containers/image/version"
)
const (

View File

@@ -0,0 +1,4 @@
package version
// Version is the version of the build.
const Version = "0.1.14-dev"