Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
18
vendor/github.com/containers/image/signature/docker.go
generated
vendored
18
vendor/github.com/containers/image/signature/docker.go
generated
vendored
|
@ -5,6 +5,7 @@ package signature
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
@ -16,12 +17,7 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig := privateSignature{
|
||||
Signature{
|
||||
DockerManifestDigest: manifestDigest,
|
||||
DockerReference: dockerReference,
|
||||
},
|
||||
}
|
||||
sig := newUntrustedSignature(manifestDigest, dockerReference)
|
||||
return sig.sign(mech, keyIdentity)
|
||||
}
|
||||
|
||||
|
@ -29,6 +25,10 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism,
|
|||
// using mech.
|
||||
func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte,
|
||||
expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
|
||||
expectedRef, err := reference.ParseNamed(expectedDockerReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
|
||||
validateKeyIdentity: func(keyIdentity string) error {
|
||||
if keyIdentity != expectedKeyIdentity {
|
||||
|
@ -37,7 +37,11 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
|
|||
return nil
|
||||
},
|
||||
validateSignedDockerReference: func(signedDockerReference string) error {
|
||||
if signedDockerReference != expectedDockerReference {
|
||||
signedRef, err := reference.ParseNamed(signedDockerReference)
|
||||
if err != nil {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)}
|
||||
}
|
||||
if signedRef.String() != expectedRef.String() {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
|
||||
signedDockerReference, expectedDockerReference)}
|
||||
}
|
||||
|
|
101
vendor/github.com/containers/image/signature/docker_test.go
generated
vendored
Normal file
101
vendor/github.com/containers/image/signature/docker_test.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
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)
|
||||
|
||||
// Verification using a different canonicalization of TestImageSignatureReference
|
||||
sig, err = VerifyDockerManifestSignature(signature, manifest, "docker.io/"+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.
|
||||
|
||||
// Invalid docker reference on input
|
||||
sig, err = VerifyDockerManifestSignature(signature, manifest, "UPPERCASEISINVALID", mech, TestKeyFingerprint)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, sig)
|
||||
|
||||
// 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)
|
||||
|
||||
// Invalid reference in the signature
|
||||
invalidReferenceSignature, err := ioutil.ReadFile("fixtures/invalid-reference.signature")
|
||||
sig, err = VerifyDockerManifestSignature(invalidReferenceSignature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint)
|
||||
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)
|
||||
}
|
4
vendor/github.com/containers/image/signature/fixtures/.gitignore
generated
vendored
Normal file
4
vendor/github.com/containers/image/signature/fixtures/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*.gpg~
|
||||
/.gpg-v21-migrated
|
||||
/private-keys-v1.d
|
||||
/random_seed
|
BIN
vendor/github.com/containers/image/signature/fixtures/corrupt.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/corrupt.signature
generated
vendored
Normal file
Binary file not shown.
27
vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/manifest.json
generated
vendored
Normal file
27
vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/manifest.json
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"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"
|
||||
}
|
BIN
vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-2
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-2
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/dir-img-valid/signature-1
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/dir-img-valid/signature-1
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/double.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/double.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/expired.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/expired.signature
generated
vendored
Normal file
Binary file not shown.
26
vendor/github.com/containers/image/signature/fixtures/image.manifest.json
generated
vendored
Normal file
26
vendor/github.com/containers/image/signature/fixtures/image.manifest.json
generated
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
BIN
vendor/github.com/containers/image/signature/fixtures/image.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/image.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/invalid-blob.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/invalid-blob.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/invalid-reference.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/invalid-reference.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/no-optional-fields.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/no-optional-fields.signature
generated
vendored
Normal file
Binary file not shown.
96
vendor/github.com/containers/image/signature/fixtures/policy.json
generated
vendored
Normal file
96
vendor/github.com/containers/image/signature/fixtures/policy.json
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"default": [
|
||||
{
|
||||
"type": "reject"
|
||||
}
|
||||
],
|
||||
"transports": {
|
||||
"dir": {
|
||||
"": [
|
||||
{
|
||||
"type": "insecureAcceptAnything"
|
||||
}
|
||||
]
|
||||
},
|
||||
"docker": {
|
||||
"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",
|
||||
"signedIdentity": {
|
||||
"type": "matchRepoDigestOrExact"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
19
vendor/github.com/containers/image/signature/fixtures/public-key.gpg
generated
vendored
Normal file
19
vendor/github.com/containers/image/signature/fixtures/public-key.gpg
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
-----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-----
|
BIN
vendor/github.com/containers/image/signature/fixtures/pubring.gpg
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/pubring.gpg
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/secring.gpg
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/secring.gpg
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/trustdb.gpg
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/trustdb.gpg
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/unknown-key.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/unknown-key.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/unsigned-encrypted.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/unsigned-encrypted.signature
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/containers/image/signature/fixtures/unsigned-literal.signature
generated
vendored
Normal file
BIN
vendor/github.com/containers/image/signature/fixtures/unsigned-literal.signature
generated
vendored
Normal file
Binary file not shown.
11
vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json
generated
vendored
Normal file
11
vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "mitr/buxybox",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
],
|
||||
"history": [
|
||||
],
|
||||
"signatures": 1
|
||||
}
|
14
vendor/github.com/containers/image/signature/fixtures_info_test.go
generated
vendored
Normal file
14
vendor/github.com/containers/image/signature/fixtures_info_test.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package signature
|
||||
|
||||
import "github.com/opencontainers/go-digest"
|
||||
|
||||
const (
|
||||
// TestImageManifestDigest is the Docker manifest digest of "image.manifest.json"
|
||||
TestImageManifestDigest = digest.Digest("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"
|
||||
// TestKeyShortID is the short ID of the private key in this directory.
|
||||
TestKeyShortID = "DB72F2188BB46CC8"
|
||||
)
|
19
vendor/github.com/containers/image/signature/json.go
generated
vendored
19
vendor/github.com/containers/image/signature/json.go
generated
vendored
|
@ -29,6 +29,23 @@ func validateExactMapKeys(m map[string]interface{}, expectedKeys ...string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
// int64Field returns a member fieldName of m, if it is an int64, or an error.
|
||||
func int64Field(m map[string]interface{}, fieldName string) (int64, error) {
|
||||
untyped, ok := m[fieldName]
|
||||
if !ok {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s missing", fieldName))
|
||||
}
|
||||
f, ok := untyped.(float64)
|
||||
if !ok {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s is not a number", fieldName))
|
||||
}
|
||||
v := int64(f)
|
||||
if float64(v) != f {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s is not an integer", fieldName))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// mapField returns a member fieldName of m, if it is a JSON map, or an error.
|
||||
func mapField(m map[string]interface{}, fieldName string) (map[string]interface{}, error) {
|
||||
untyped, ok := m[fieldName]
|
||||
|
@ -50,7 +67,7 @@ func stringField(m map[string]interface{}, fieldName string) (string, error) {
|
|||
}
|
||||
v, ok := untyped.(string)
|
||||
if !ok {
|
||||
return "", jsonFormatError(fmt.Sprintf("Field %s is not a JSON object", fieldName))
|
||||
return "", jsonFormatError(fmt.Sprintf("Field %s is not a string", fieldName))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
|
182
vendor/github.com/containers/image/signature/json_test.go
generated
vendored
Normal file
182
vendor/github.com/containers/image/signature/json_test.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"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 TestInt64Field(t *testing.T) {
|
||||
// Field not found
|
||||
_, err := int64Field(mSI{"a": "x"}, "b")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Field has a wrong type
|
||||
_, err = int64Field(mSI{"a": "string"}, "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
for _, value := range []float64{
|
||||
0.5, // Fractional input
|
||||
math.Inf(1), // Infinity
|
||||
math.NaN(), // NaN
|
||||
} {
|
||||
_, err = int64Field(mSI{"a": value}, "a")
|
||||
assert.Error(t, err, fmt.Sprintf("%f", value))
|
||||
}
|
||||
|
||||
// Success
|
||||
// The float64 type has 53 bits of effective precision, so ±1FFFFFFFFFFFFF is the
|
||||
// range of integer values which can all be represented exactly (beyond that,
|
||||
// some are representable if they are divisible by a high enough power of 2,
|
||||
// but most are not).
|
||||
for _, value := range []int64{0, 1, -1, 0x1FFFFFFFFFFFFF, -0x1FFFFFFFFFFFFF} {
|
||||
testName := fmt.Sprintf("%d", value)
|
||||
v, err := int64Field(mSI{"a": float64(value), "b": nil}, "a")
|
||||
require.NoError(t, err, testName)
|
||||
assert.Equal(t, value, v, testName)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
38
vendor/github.com/containers/image/signature/mechanism.go
generated
vendored
38
vendor/github.com/containers/image/signature/mechanism.go
generated
vendored
|
@ -4,9 +4,13 @@ package signature
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/mtrmac/gpgme"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
|
||||
|
@ -21,6 +25,12 @@ type SigningMechanism interface {
|
|||
Sign(input []byte, keyIdentity string) ([]byte, error)
|
||||
// Verify parses unverifiedSignature and returns the content and the signer's identity
|
||||
Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error)
|
||||
// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,
|
||||
// along with a short identifier of the key used for signing.
|
||||
// WARNING: The short key identifier (which correponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls ot this interface, and
|
||||
// the values may have no recognizable relationship if the public key is not available.
|
||||
UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error)
|
||||
}
|
||||
|
||||
// A GPG/OpenPGP signing mechanism.
|
||||
|
@ -119,3 +129,31 @@ func (m gpgSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte
|
|||
}
|
||||
return signedBuffer.Bytes(), sig.Fingerprint, nil
|
||||
}
|
||||
|
||||
// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,
|
||||
// along with a short identifier of the key used for signing.
|
||||
// WARNING: The short key identifier (which correponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls ot this interface, and
|
||||
// the values may have no recognizable relationship if the public key is not available.
|
||||
func (m gpgSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
|
||||
// This uses the Golang-native OpenPGP implementation instead of gpgme because we are not doing any cryptography.
|
||||
md, err := openpgp.ReadMessage(bytes.NewReader(untrustedSignature), openpgp.EntityList{}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if !md.IsSigned {
|
||||
return nil, "", errors.New("The input is not a signature")
|
||||
}
|
||||
content, err := ioutil.ReadAll(md.UnverifiedBody)
|
||||
if err != nil {
|
||||
// Coverage: An error during reading the body can happen only if
|
||||
// 1) the message is encrypted, which is not our case (and we don’t give ReadMessage the key
|
||||
// to decrypt the contents anyway), or
|
||||
// 2) the message is signed AND we give ReadMessage a correspnding public key, which we don’t.
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Uppercase the key ID for minimal consistency with the gpgme-returned fingerprints
|
||||
// (but note that key ID is a suffix of the fingerprint only for V4 keys, not V3)!
|
||||
return content, strings.ToUpper(fmt.Sprintf("%016X", md.SignedByKeyId)), nil
|
||||
}
|
||||
|
|
205
vendor/github.com/containers/image/signature/mechanism_test.go
generated
vendored
Normal file
205
vendor/github.com/containers/image/signature/mechanism_test.go
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
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.
|
||||
}
|
||||
|
||||
func TestGPGSigningMechanismUntrustedSignatureContents(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
|
||||
// A valid signature
|
||||
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("This is not JSON\n"), content)
|
||||
assert.Equal(t, TestKeyShortID, shortKeyID)
|
||||
|
||||
// Completely invalid signature.
|
||||
_, _, err = mech.UntrustedSignatureContents([]byte{})
|
||||
assert.Error(t, err)
|
||||
|
||||
_, _, err = mech.UntrustedSignatureContents([]byte("invalid signature"))
|
||||
assert.Error(t, err)
|
||||
|
||||
// Literal packet, not a signature
|
||||
signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Encrypted data, not a signature.
|
||||
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Expired signature
|
||||
signature, err = ioutil.ReadFile("./fixtures/expired.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("This signature is expired.\n"), content)
|
||||
assert.Equal(t, TestKeyShortID, shortKeyID)
|
||||
|
||||
// Corrupt signature
|
||||
signature, err = ioutil.ReadFile("./fixtures/corrupt.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic ","timestamp":1458239713}}`), content)
|
||||
assert.Equal(t, TestKeyShortID, shortKeyID)
|
||||
|
||||
// Valid signature with an unknown key
|
||||
signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature")
|
||||
require.NoError(t, err)
|
||||
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic 0.1.13-dev","timestamp":1464633474}}`), content)
|
||||
assert.Equal(t, "E5476D1110D07803", shortKeyID)
|
||||
}
|
1366
vendor/github.com/containers/image/signature/policy_config_test.go
generated
vendored
Normal file
1366
vendor/github.com/containers/image/signature/policy_config_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
24
vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go
generated
vendored
Normal file
24
vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
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)
|
||||
}
|
264
vendor/github.com/containers/image/signature/policy_eval_signedby_test.go
generated
vendored
Normal file
264
vendor/github.com/containers/image/signature/policy_eval_signedby_test.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/directory"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// dirImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference.
|
||||
// The caller must call .Close() on the returned UnparsedImage.
|
||||
func dirImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage {
|
||||
ref, err := reference.ParseNamed(dockerReference)
|
||||
require.NoError(t, err)
|
||||
return dirImageMockWithRef(t, dir, refImageReferenceMock{ref})
|
||||
}
|
||||
|
||||
// dirImageMockWithRef returns a types.UnparsedImage for a directory, claiming a specified ref.
|
||||
// The caller must call .Close() on the returned UnparsedImage.
|
||||
func dirImageMockWithRef(t *testing.T, dir string, ref types.ImageReference) types.UnparsedImage {
|
||||
srcRef, err := directory.NewReference(dir)
|
||||
require.NoError(t, err)
|
||||
src, err := srcRef.NewImageSource(nil, nil)
|
||||
require.NoError(t, err)
|
||||
return image.UnparsedFromSource(&dirImageSourceMock{
|
||||
ImageSource: src,
|
||||
ref: ref,
|
||||
})
|
||||
}
|
||||
|
||||
// dirImageSourceMock inherits dirImageSource, but overrides its Reference method.
|
||||
type dirImageSourceMock struct {
|
||||
types.ImageSource
|
||||
ref types.ImageReference
|
||||
}
|
||||
|
||||
func (d *dirImageSourceMock) Reference() types.ImageReference {
|
||||
return d.ref
|
||||
}
|
||||
|
||||
func TestPRSignedByIsSignatureAuthorAccepted(t *testing.T) {
|
||||
ktGPG := SBKeyTypeGPGKeys
|
||||
prm := NewPRMMatchExact()
|
||||
testImage := dirImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer testImage.Close()
|
||||
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(t, "fixtures/dir-img-no-manifest", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-manifest-digest-error", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, invalidSigDir, "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-unsigned", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-valid", "testing/manifest:notlatest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-valid-2", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-mixed", "testing/manifest:latest")
|
||||
defer image.Close()
|
||||
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(t, "fixtures/dir-img-valid-2", "testing/manifest:notlatest")
|
||||
defer image.Close()
|
||||
pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm)
|
||||
require.NoError(t, err)
|
||||
allowed, err = pr.isRunningImageAllowed(image)
|
||||
assertRunningRejectedPolicyRequirement(t, allowed, err)
|
||||
}
|
74
vendor/github.com/containers/image/signature/policy_eval_simple_test.go
generated
vendored
Normal file
74
vendor/github.com/containers/image/signature/policy_eval_simple_test.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
// nameOnlyImageMock is a mock of types.UnparsedImage which only allows transports.ImageName to work
|
||||
type nameOnlyImageMock struct {
|
||||
forbiddenImageMock
|
||||
}
|
||||
|
||||
func (nameOnlyImageMock) Reference() types.ImageReference {
|
||||
return nameOnlyImageReferenceMock("== StringWithinTransport mock")
|
||||
}
|
||||
|
||||
// nameOnlyImageReferenceMock is a mock of types.ImageReference which only allows transports.ImageName to work, returning self.
|
||||
type nameOnlyImageReferenceMock string
|
||||
|
||||
func (ref nameOnlyImageReferenceMock) Transport() types.ImageTransport {
|
||||
return nameImageTransportMock("== Transport mock")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) StringWithinTransport() string {
|
||||
return string(ref)
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) DockerReference() reference.Named {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref nameOnlyImageReferenceMock) DeleteImage(ctx *types.SystemContext) error {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
func TestPRInsecureAcceptAnythingIsSignatureAuthorAccepted(t *testing.T) {
|
||||
pr := NewPRInsecureAcceptAnything()
|
||||
// Pass nil signature to, kind of, test that the return value does not depend on it.
|
||||
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nameOnlyImageMock{}, nil)
|
||||
assertSARUnknown(t, sar, parsedSig, err)
|
||||
}
|
||||
|
||||
func TestPRInsecureAcceptAnythingIsRunningImageAllowed(t *testing.T) {
|
||||
pr := NewPRInsecureAcceptAnything()
|
||||
res, err := pr.isRunningImageAllowed(nameOnlyImageMock{})
|
||||
assertRunningAllowed(t, res, err)
|
||||
}
|
||||
|
||||
func TestPRRejectIsSignatureAuthorAccepted(t *testing.T) {
|
||||
pr := NewPRReject()
|
||||
// Pass nil signature to, kind of, test that the return value does not depend on it.
|
||||
sar, parsedSig, err := pr.isSignatureAuthorAccepted(nameOnlyImageMock{}, nil)
|
||||
assertSARRejectedPolicyRequirement(t, sar, parsedSig, err)
|
||||
}
|
||||
|
||||
func TestPRRejectIsRunningImageAllowed(t *testing.T) {
|
||||
pr := NewPRReject()
|
||||
res, err := pr.isRunningImageAllowed(nameOnlyImageMock{})
|
||||
assertRunningRejectedPolicyRequirement(t, res, err)
|
||||
}
|
488
vendor/github.com/containers/image/signature/policy_eval_test.go
generated
vendored
Normal file
488
vendor/github.com/containers/image/signature/policy_eval_test.go
generated
vendored
Normal file
|
@ -0,0 +1,488 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
"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)
|
||||
}
|
||||
|
||||
// pcImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference
|
||||
// and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently.
|
||||
type pcImageReferenceMock struct {
|
||||
transportName string
|
||||
ref reference.Named
|
||||
}
|
||||
|
||||
func (ref pcImageReferenceMock) Transport() types.ImageTransport {
|
||||
return nameImageTransportMock(ref.transportName)
|
||||
}
|
||||
func (ref pcImageReferenceMock) StringWithinTransport() string {
|
||||
// We use this in error messages, so sadly we must return something.
|
||||
return "== StringWithinTransport mock"
|
||||
}
|
||||
func (ref pcImageReferenceMock) DockerReference() reference.Named {
|
||||
return ref.ref
|
||||
}
|
||||
func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
res, err := policyconfiguration.DockerReferenceIdentity(ref.ref)
|
||||
if res == "" || err != nil {
|
||||
panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
func (ref pcImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
if ref.ref == nil {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
|
||||
}
|
||||
func (ref pcImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref pcImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref pcImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref pcImageReferenceMock) DeleteImage(ctx *types.SystemContext) error {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
func TestPolicyContextRequirementsForImageRef(t *testing.T) {
|
||||
ktGPG := SBKeyTypeGPGKeys
|
||||
prm := NewPRMMatchRepoDigestOrExact()
|
||||
|
||||
policy := &Policy{
|
||||
Default: PolicyRequirements{NewPRReject()},
|
||||
Transports: map[string]PolicyTransportScopes{},
|
||||
}
|
||||
// Just put _something_ into the PolicyTransportScopes 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 _, t := range []struct{ transport, scope string }{
|
||||
{"docker", ""},
|
||||
{"docker", "unmatched"},
|
||||
{"docker", "deep.com"},
|
||||
{"docker", "deep.com/n1"},
|
||||
{"docker", "deep.com/n1/n2"},
|
||||
{"docker", "deep.com/n1/n2/n3"},
|
||||
{"docker", "deep.com/n1/n2/n3/repo"},
|
||||
{"docker", "deep.com/n1/n2/n3/repo:tag2"},
|
||||
{"atomic", "unmatched"},
|
||||
} {
|
||||
if _, ok := policy.Transports[t.transport]; !ok {
|
||||
policy.Transports[t.transport] = PolicyTransportScopes{}
|
||||
}
|
||||
policy.Transports[t.transport][t.scope] = PolicyRequirements{xNewPRSignedByKeyData(ktGPG, []byte(t.transport+t.scope), prm)}
|
||||
}
|
||||
|
||||
pc, err := NewPolicyContext(policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, c := range []struct{ inputTransport, input, matchedTransport, matched string }{
|
||||
// Full match
|
||||
{"docker", "deep.com/n1/n2/n3/repo:tag2", "docker", "deep.com/n1/n2/n3/repo:tag2"},
|
||||
// Namespace matches
|
||||
{"docker", "deep.com/n1/n2/n3/repo:nottag2", "docker", "deep.com/n1/n2/n3/repo"},
|
||||
{"docker", "deep.com/n1/n2/n3/notrepo:tag2", "docker", "deep.com/n1/n2/n3"},
|
||||
{"docker", "deep.com/n1/n2/notn3/repo:tag2", "docker", "deep.com/n1/n2"},
|
||||
{"docker", "deep.com/n1/notn2/n3/repo:tag2", "docker", "deep.com/n1"},
|
||||
// Host name match
|
||||
{"docker", "deep.com/notn1/n2/n3/repo:tag2", "docker", "deep.com"},
|
||||
// Default
|
||||
{"docker", "this.doesnt/match:anything", "docker", ""},
|
||||
// No match within a matched transport which doesn't have a "" scope
|
||||
{"atomic", "this.doesnt/match:anything", "", ""},
|
||||
// No configuration available for this transport at all
|
||||
{"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.Named.
|
||||
} {
|
||||
var expected PolicyRequirements
|
||||
if c.matchedTransport != "" {
|
||||
e, ok := policy.Transports[c.matchedTransport][c.matched]
|
||||
require.True(t, ok, fmt.Sprintf("case %s:%s: expected reqs not found", c.inputTransport, c.input))
|
||||
expected = e
|
||||
} else {
|
||||
expected = policy.Default
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNamed(c.input)
|
||||
require.NoError(t, err)
|
||||
reqs := pc.requirementsForImageRef(pcImageReferenceMock{c.inputTransport, ref})
|
||||
comment := fmt.Sprintf("case %s:%s: %#v", c.inputTransport, c.input, reqs[0])
|
||||
// Do not use 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)
|
||||
}
|
||||
}
|
||||
|
||||
// pcImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference and implementing PolicyConfigurationIdentity/PolicyConfigurationNamespaces.
|
||||
// The caller must call .Close() on the returned Image.
|
||||
func pcImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage {
|
||||
ref, err := reference.ParseNamed(dockerReference)
|
||||
require.NoError(t, err)
|
||||
return dirImageMockWithRef(t, dir, pcImageReferenceMock{"docker", ref})
|
||||
}
|
||||
|
||||
func TestPolicyContextGetSignaturesWithAcceptedAuthor(t *testing.T) {
|
||||
expectedSig := &Signature{
|
||||
DockerManifestDigest: TestImageManifestDigest,
|
||||
DockerReference: "testing/manifest:latest",
|
||||
}
|
||||
|
||||
pc, err := NewPolicyContext(&Policy{
|
||||
Default: PolicyRequirements{NewPRReject()},
|
||||
Transports: map[string]PolicyTransportScopes{
|
||||
"docker": {
|
||||
"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
|
||||
img := pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err := pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*Signature{expectedSig}, sigs)
|
||||
|
||||
// Two signatures
|
||||
// FIXME? Use really different signatures for this?
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*Signature{expectedSig, expectedSig}, sigs)
|
||||
|
||||
// No signatures
|
||||
img = pcImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
// Only invalid signatures
|
||||
img = pcImageMock(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
// 1 invalid, 1 valid signature (in this order)
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*Signature{expectedSig}, sigs)
|
||||
|
||||
// Two sarAccepted results for one signature
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:twoAccepts")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*Signature{expectedSig}, sigs)
|
||||
|
||||
// sarAccepted+sarRejected for a signature
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:acceptReject")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
// sarAccepted+sarUnknown for a signature
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:acceptUnknown")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*Signature{expectedSig}, sigs)
|
||||
|
||||
// sarRejected+sarUnknown for a signature
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:rejectUnknown")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
// sarUnknown only
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:unknown")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:unknown2")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sigs)
|
||||
|
||||
// Empty list of requirements (invalid)
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
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)
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = destroyedPC.GetSignaturesWithAcceptedAuthor(img)
|
||||
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.
|
||||
|
||||
// Error reading signatures.
|
||||
invalidSigDir := createInvalidSigDir(t)
|
||||
defer os.RemoveAll(invalidSigDir)
|
||||
img = pcImageMock(t, invalidSigDir, "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
sigs, err = pc.GetSignaturesWithAcceptedAuthor(img)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, sigs)
|
||||
}
|
||||
|
||||
func TestPolicyContextIsRunningImageAllowed(t *testing.T) {
|
||||
pc, err := NewPolicyContext(&Policy{
|
||||
Default: PolicyRequirements{NewPRReject()},
|
||||
Transports: map[string]PolicyTransportScopes{
|
||||
"docker": {
|
||||
"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
|
||||
img := pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err := pc.IsRunningImageAllowed(img)
|
||||
assertRunningAllowed(t, res, err)
|
||||
|
||||
// Two signatures
|
||||
// FIXME? Use really different signatures for this?
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningAllowed(t, res, err)
|
||||
|
||||
// No signatures
|
||||
img = pcImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningRejectedPolicyRequirement(t, res, err)
|
||||
|
||||
// Only invalid signatures
|
||||
img = pcImageMock(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningRejectedPolicyRequirement(t, res, err)
|
||||
|
||||
// 1 invalid, 1 valid signature (in this order)
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningAllowed(t, res, err)
|
||||
|
||||
// Two allowed results
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:twoAllows")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningAllowed(t, res, err)
|
||||
|
||||
// Allow + deny results
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:allowDeny")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningRejectedPolicyRequirement(t, res, err)
|
||||
|
||||
// prReject works
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:reject")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningRejectedPolicyRequirement(t, res, err)
|
||||
|
||||
// prInsecureAcceptAnything works
|
||||
img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:acceptAnything")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
assertRunningAllowed(t, res, err)
|
||||
|
||||
// Empty list of requirements (invalid)
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements")
|
||||
defer img.Close()
|
||||
res, err = pc.IsRunningImageAllowed(img)
|
||||
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)
|
||||
img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest")
|
||||
defer img.Close()
|
||||
res, err = destroyedPC.IsRunningImageAllowed(img)
|
||||
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.
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
370
vendor/github.com/containers/image/signature/policy_reference_match_test.go
generated
vendored
Normal file
370
vendor/github.com/containers/image/signature/policy_reference_match_test.go
generated
vendored
Normal file
|
@ -0,0 +1,370 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/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"
|
||||
digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
digestSuffixOther = "@sha256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
)
|
||||
|
||||
func TestParseImageAndDockerReference(t *testing.T) {
|
||||
const (
|
||||
ok1 = "busybox"
|
||||
ok2 = fullRHELRef
|
||||
bad1 = "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES"
|
||||
bad2 = ""
|
||||
)
|
||||
// Success
|
||||
ref, err := reference.ParseNamed(ok1)
|
||||
require.NoError(t, err)
|
||||
r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ok1, r1.String())
|
||||
assert.Equal(t, ok2, r2.String())
|
||||
|
||||
// Unidentified images are rejected.
|
||||
_, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2)
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, PolicyRequirementError(""), err)
|
||||
|
||||
// Failures
|
||||
for _, refs := range [][]string{
|
||||
{bad1, ok2},
|
||||
{ok1, bad2},
|
||||
{bad1, bad2},
|
||||
} {
|
||||
ref, err := reference.ParseNamed(refs[0])
|
||||
if err == nil {
|
||||
_, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1])
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refImageMock is a mock of types.UnparsedImage which returns itself in Reference().DockerReference.
|
||||
type refImageMock struct{ reference.Named }
|
||||
|
||||
func (ref refImageMock) Reference() types.ImageReference {
|
||||
return refImageReferenceMock{ref.Named}
|
||||
}
|
||||
func (ref refImageMock) Close() {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageMock) Manifest() ([]byte, string, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageMock) Signatures() ([][]byte, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
// refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference.
|
||||
type refImageReferenceMock struct{ reference.Named }
|
||||
|
||||
func (ref refImageReferenceMock) Transport() types.ImageTransport {
|
||||
// We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that.
|
||||
if ref.Named == nil {
|
||||
return nameImageTransportMock("== Transport mock")
|
||||
}
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) StringWithinTransport() string {
|
||||
// We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that.
|
||||
if ref.Named == nil {
|
||||
return "== StringWithinTransport for an image with no Docker support"
|
||||
}
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) DockerReference() reference.Named {
|
||||
return ref.Named
|
||||
}
|
||||
func (ref refImageReferenceMock) PolicyConfigurationIdentity() string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) PolicyConfigurationNamespaces() []string {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref refImageReferenceMock) DeleteImage(ctx *types.SystemContext) error {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
// nameImageTransportMock is a mock of types.ImageTransport which returns itself in Name.
|
||||
type nameImageTransportMock string
|
||||
|
||||
func (name nameImageTransportMock) Name() string {
|
||||
return string(name)
|
||||
}
|
||||
func (name nameImageTransportMock) ParseReference(reference string) (types.ImageReference, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (name nameImageTransportMock) ValidatePolicyConfigurationScope(scope string) error {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
type prmSymmetricTableTest struct {
|
||||
refA, refB string
|
||||
result bool
|
||||
}
|
||||
|
||||
// Test cases for exact reference match. The behavior is supposed to be symmetric.
|
||||
var prmExactMatchTestTable = []prmSymmetricTableTest{
|
||||
// Success, simple matches
|
||||
{"busybox:latest", "busybox:latest", true},
|
||||
{fullRHELRef, fullRHELRef, true},
|
||||
{"busybox" + digestSuffix, "busybox" + digestSuffix, true}, // NOTE: This is not documented; signing digests is not recommended at this time.
|
||||
// Non-canonical reference format is canonicalized
|
||||
{"library/busybox:latest", "busybox:latest", true},
|
||||
{"docker.io/library/busybox:latest", "busybox:latest", true},
|
||||
{"library/busybox" + digestSuffix, "busybox" + digestSuffix, 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},
|
||||
{"busybox" + digestSuffix, "notbusybox" + digestSuffix, false},
|
||||
{"busybox:latest", "busybox" + digestSuffix, false},
|
||||
{"busybox" + digestSuffix, "busybox" + digestSuffixOther, false},
|
||||
// NameOnly references
|
||||
{"busybox", "busybox:latest", false},
|
||||
{"busybox", "busybox" + digestSuffix, false},
|
||||
{"busybox", "busybox", false},
|
||||
// References with both tags and digests: `reference.WithName` essentially drops the tag.
|
||||
// This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means,
|
||||
// even if the tag may reflect a different user intent.
|
||||
// NOTE: Again, this is not documented behavior; the recommendation is to sign tags, not digests, and then tag-and-digest references won’t match the signed identity.
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true},
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, false},
|
||||
{"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, // Ugly. Do not rely on this.
|
||||
{"busybox:latest" + digestSuffix, "busybox" + digestSuffix, true}, // Ugly. Do not rely on this.
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest", false},
|
||||
// Invalid format
|
||||
{"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", 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. The behavior is supposed to be symmetric.
|
||||
var prmRepositoryMatchTestTable = []prmSymmetricTableTest{
|
||||
// Success, simple matches
|
||||
{"busybox:latest", "busybox:latest", true},
|
||||
{fullRHELRef, fullRHELRef, true},
|
||||
{"busybox" + digestSuffix, "busybox" + digestSuffix, true}, // NOTE: This is not documented; signing digests is not recommended at this time.
|
||||
// Non-canonical reference format is canonicalized
|
||||
{"library/busybox:latest", "busybox:latest", true},
|
||||
{"docker.io/library/busybox:latest", "busybox:latest", true},
|
||||
{"library/busybox" + digestSuffix, "busybox" + digestSuffix, 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},
|
||||
{"busybox:latest", "busybox" + digestSuffix, true},
|
||||
{"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.)
|
||||
// The same as above, but with defaulted tags (should not actually happen)
|
||||
{"busybox", "busybox:notlatest", true},
|
||||
{fullRHELRef, untaggedRHELRef, true},
|
||||
{"busybox", "busybox" + digestSuffix, true},
|
||||
{"library/busybox", "busybox", true},
|
||||
{"docker.io/library/busybox", "busybox", true},
|
||||
// Mismatch
|
||||
{"busybox:latest", "notbusybox:latest", false},
|
||||
{"hostname/library/busybox:latest", "busybox:notlatest", false},
|
||||
{"busybox:latest", fullRHELRef, false},
|
||||
{"busybox" + digestSuffix, "notbusybox" + digestSuffix, false},
|
||||
// References with both tags and digests: `reference.WithName` essentially drops the tag, and we ignore both anyway.
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true},
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true},
|
||||
{"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true},
|
||||
{"busybox:latest" + digestSuffix, "busybox" + digestSuffix, true},
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest", true},
|
||||
// Invalid format
|
||||
{"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false},
|
||||
{"", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false},
|
||||
// Even if they are exactly equal, invalid values are rejected.
|
||||
{"INVALID", "INVALID", false},
|
||||
}
|
||||
|
||||
func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) {
|
||||
// This assumes that all ways to obtain a reference.Named perform equivalent validation,
|
||||
// and therefore values refused by reference.ParseNamed can not happen in practice.
|
||||
parsedImageRef, err := reference.ParseNamed(imageRef)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res := prm.matchesDockerReference(refImageMock{parsedImageRef}, sigRef)
|
||||
assert.Equal(t, result, res, fmt.Sprintf("%s vs. %s", imageRef, sigRef))
|
||||
}
|
||||
|
||||
func TestPRMMatchExactMatchesDockerReference(t *testing.T) {
|
||||
prm := NewPRMMatchExact()
|
||||
for _, test := range prmExactMatchTestTable {
|
||||
testImageAndSig(t, prm, test.refA, test.refB, test.result)
|
||||
testImageAndSig(t, prm, test.refB, test.refA, test.result)
|
||||
}
|
||||
// Even if they are signed with an empty string as a reference, unidentified images are rejected.
|
||||
res := prm.matchesDockerReference(refImageMock{nil}, "")
|
||||
assert.False(t, res, `unidentified vs. ""`)
|
||||
}
|
||||
|
||||
func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) {
|
||||
prm := NewPRMMatchRepoDigestOrExact()
|
||||
|
||||
// prmMatchRepoDigestOrExact is a middle ground between prmMatchExact and prmMatchRepository:
|
||||
// It accepts anything prmMatchExact accepts,…
|
||||
for _, test := range prmExactMatchTestTable {
|
||||
if test.result == true {
|
||||
testImageAndSig(t, prm, test.refA, test.refB, test.result)
|
||||
testImageAndSig(t, prm, test.refB, test.refA, test.result)
|
||||
}
|
||||
}
|
||||
// … and it rejects everything prmMatchRepository rejects.
|
||||
for _, test := range prmRepositoryMatchTestTable {
|
||||
if test.result == false {
|
||||
testImageAndSig(t, prm, test.refA, test.refB, test.result)
|
||||
testImageAndSig(t, prm, test.refB, test.refA, test.result)
|
||||
}
|
||||
}
|
||||
|
||||
// The other cases, possibly assymetrical:
|
||||
for _, test := range []struct {
|
||||
imageRef, sigRef string
|
||||
result bool
|
||||
}{
|
||||
// Tag mismatch
|
||||
{"busybox:latest", "busybox:notlatest", false},
|
||||
{fullRHELRef + "tagsuffix", fullRHELRef, false},
|
||||
{"library/busybox:latest", "busybox:notlatest", false},
|
||||
{"busybox:latest", "library/busybox:notlatest", false},
|
||||
{"docker.io/library/busybox:notlatest", "busybox:latest", false},
|
||||
{"busybox:notlatest", "docker.io/library/busybox:latest", false},
|
||||
// NameOnly references
|
||||
{"busybox", "busybox:latest", false},
|
||||
{"busybox:latest", "busybox", false},
|
||||
{"busybox", "busybox" + digestSuffix, false},
|
||||
{"busybox" + digestSuffix, "busybox", false},
|
||||
{fullRHELRef, untaggedRHELRef, false},
|
||||
{"busybox", "busybox", false},
|
||||
// Tag references only accept signatures with matching tags.
|
||||
{"busybox:latest", "busybox" + digestSuffix, false},
|
||||
// Digest references accept any signature with matching repository.
|
||||
{"busybox" + digestSuffix, "busybox:latest", true},
|
||||
{"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.)
|
||||
// References with both tags and digests: `reference.WithName` essentially drops the tag.
|
||||
// This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means,
|
||||
// even if the tag may reflect a different user intent.
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest", true},
|
||||
{"busybox:latest" + digestSuffix, "busybox:notlatest", true},
|
||||
{"busybox:latest", "busybox:latest" + digestSuffix, false},
|
||||
{"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.)
|
||||
{"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, true}, // Ugly. Do not rely on this.
|
||||
} {
|
||||
testImageAndSig(t, prm, test.imageRef, test.sigRef, test.result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRMMatchRepositoryMatchesDockerReference(t *testing.T) {
|
||||
prm := NewPRMMatchRepository()
|
||||
for _, test := range prmRepositoryMatchTestTable {
|
||||
testImageAndSig(t, prm, test.refA, test.refB, test.result)
|
||||
testImageAndSig(t, prm, test.refB, test.refA, test.result)
|
||||
}
|
||||
// Even if they are signed with an empty string as a reference, unidentified images are rejected.
|
||||
res := prm.matchesDockerReference(refImageMock{nil}, "")
|
||||
assert.False(t, res, `unidentified vs. ""`)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// forbiddenImageMock is a mock of types.UnparsedImage which ensures Reference is not called
|
||||
type forbiddenImageMock struct{}
|
||||
|
||||
func (ref forbiddenImageMock) Reference() types.ImageReference {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref forbiddenImageMock) Close() {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref forbiddenImageMock) Manifest() ([]byte, string, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
func (ref forbiddenImageMock) Signatures() ([][]byte, error) {
|
||||
panic("unexpected call to a mock function")
|
||||
}
|
||||
|
||||
func testExactPRMAndSig(t *testing.T, prmFactory func(string) PolicyReferenceMatch, imageRef, sigRef string, result bool) {
|
||||
prm := prmFactory(imageRef)
|
||||
res := prm.matchesDockerReference(forbiddenImageMock{}, sigRef)
|
||||
assert.Equal(t, result, res, fmt.Sprintf("%s vs. %s", imageRef, sigRef))
|
||||
}
|
||||
|
||||
func prmExactReferenceFactory(ref string) PolicyReferenceMatch {
|
||||
// Do not use NewPRMExactReference, we want to also test the case with an invalid DockerReference,
|
||||
// even though NewPRMExactReference should never let it happen.
|
||||
return &prmExactReference{DockerReference: ref}
|
||||
}
|
||||
|
||||
func TestPRMExactReferenceMatchesDockerReference(t *testing.T) {
|
||||
for _, test := range prmExactMatchTestTable {
|
||||
testExactPRMAndSig(t, prmExactReferenceFactory, test.refA, test.refB, test.result)
|
||||
testExactPRMAndSig(t, prmExactReferenceFactory, test.refB, test.refA, test.result)
|
||||
}
|
||||
}
|
||||
|
||||
func prmExactRepositoryFactory(ref string) PolicyReferenceMatch {
|
||||
// Do not use NewPRMExactRepository, we want to also test the case with an invalid DockerReference,
|
||||
// even though NewPRMExactRepository should never let it happen.
|
||||
return &prmExactRepository{DockerRepository: ref}
|
||||
}
|
||||
|
||||
func TestPRMExactRepositoryMatchesDockerReference(t *testing.T) {
|
||||
for _, test := range prmRepositoryMatchTestTable {
|
||||
testExactPRMAndSig(t, prmExactRepositoryFactory, test.refA, test.refB, test.result)
|
||||
testExactPRMAndSig(t, prmExactRepositoryFactory, test.refB, test.refA, test.result)
|
||||
}
|
||||
}
|
159
vendor/github.com/containers/image/signature/signature.go
generated
vendored
159
vendor/github.com/containers/image/signature/signature.go
generated
vendored
|
@ -27,37 +27,74 @@ func (err InvalidSignatureError) Error() string {
|
|||
}
|
||||
|
||||
// Signature is a parsed content of a signature.
|
||||
// The only way to get this structure from a blob should be as a return value from a successful call to verifyAndExtractSignature below.
|
||||
type Signature struct {
|
||||
DockerManifestDigest digest.Digest
|
||||
DockerReference string // FIXME: more precise type?
|
||||
}
|
||||
|
||||
// Wrap signature to add to it some methods which we don't want to make public.
|
||||
type privateSignature struct {
|
||||
Signature
|
||||
// untrustedSignature is a parsed content of a signature.
|
||||
type untrustedSignature struct {
|
||||
UntrustedDockerManifestDigest digest.Digest
|
||||
UntrustedDockerReference string // FIXME: more precise type?
|
||||
UntrustedCreatorID *string
|
||||
// This is intentionally an int64; the native JSON float64 type would allow to represent _some_ sub-second precision,
|
||||
// but not nearly enough (with current timestamp values, a single unit in the last place is on the order of hundreds of nanoseconds).
|
||||
// So, this is explicitly an int64, and we reject fractional values. If we did need more precise timestamps eventually,
|
||||
// we would add another field, UntrustedTimestampNS int64.
|
||||
UntrustedTimestamp *int64
|
||||
}
|
||||
|
||||
// Compile-time check that privateSignature implements json.Marshaler
|
||||
var _ json.Marshaler = (*privateSignature)(nil)
|
||||
// UntrustedSignatureInformation is information available in an untrusted signature.
|
||||
// This may be useful when debugging signature verification failures,
|
||||
// or when managing a set of signatures on a single image.
|
||||
//
|
||||
// WARNING: Do not use the contents of this for ANY security decisions,
|
||||
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
|
||||
// There is NO REASON to expect the values to be correct, or not intentionally misleading
|
||||
// (including things like “✅ Verified by $authority”)
|
||||
type UntrustedSignatureInformation struct {
|
||||
UntrustedDockerManifestDigest digest.Digest
|
||||
UntrustedDockerReference string // FIXME: more precise type?
|
||||
UntrustedCreatorID *string
|
||||
UntrustedTimestamp *time.Time
|
||||
UntrustedShortKeyIdentifier string
|
||||
}
|
||||
|
||||
// newUntrustedSignature returns an untrustedSignature object with
|
||||
// the specified primary contents and appropriate metadata.
|
||||
func newUntrustedSignature(dockerManifestDigest digest.Digest, dockerReference string) untrustedSignature {
|
||||
// Use intermediate variables for these values so that we can take their addresses.
|
||||
// Golang guarantees that they will have a new address on every execution.
|
||||
creatorID := "atomic " + version.Version
|
||||
timestamp := time.Now().Unix()
|
||||
return untrustedSignature{
|
||||
UntrustedDockerManifestDigest: dockerManifestDigest,
|
||||
UntrustedDockerReference: dockerReference,
|
||||
UntrustedCreatorID: &creatorID,
|
||||
UntrustedTimestamp: ×tamp,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time check that untrustedSignature implements json.Marshaler
|
||||
var _ json.Marshaler = (*untrustedSignature)(nil)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s privateSignature) MarshalJSON() ([]byte, error) {
|
||||
return s.marshalJSONWithVariables(time.Now().UTC().Unix(), "atomic "+version.Version)
|
||||
}
|
||||
|
||||
// Implementation of MarshalJSON, with a caller-chosen values of the variable items to help testing.
|
||||
func (s privateSignature) marshalJSONWithVariables(timestamp int64, creatorID string) ([]byte, error) {
|
||||
if s.DockerManifestDigest == "" || s.DockerReference == "" {
|
||||
func (s untrustedSignature) MarshalJSON() ([]byte, error) {
|
||||
if s.UntrustedDockerManifestDigest == "" || s.UntrustedDockerReference == "" {
|
||||
return nil, errors.New("Unexpected empty signature content")
|
||||
}
|
||||
critical := map[string]interface{}{
|
||||
"type": signatureType,
|
||||
"image": map[string]string{"docker-manifest-digest": s.DockerManifestDigest.String()},
|
||||
"identity": map[string]string{"docker-reference": s.DockerReference},
|
||||
"image": map[string]string{"docker-manifest-digest": s.UntrustedDockerManifestDigest.String()},
|
||||
"identity": map[string]string{"docker-reference": s.UntrustedDockerReference},
|
||||
}
|
||||
optional := map[string]interface{}{
|
||||
"creator": creatorID,
|
||||
"timestamp": timestamp,
|
||||
optional := map[string]interface{}{}
|
||||
if s.UntrustedCreatorID != nil {
|
||||
optional["creator"] = *s.UntrustedCreatorID
|
||||
}
|
||||
if s.UntrustedTimestamp != nil {
|
||||
optional["timestamp"] = *s.UntrustedTimestamp
|
||||
}
|
||||
signature := map[string]interface{}{
|
||||
"critical": critical,
|
||||
|
@ -66,11 +103,11 @@ func (s privateSignature) marshalJSONWithVariables(timestamp int64, creatorID st
|
|||
return json.Marshal(signature)
|
||||
}
|
||||
|
||||
// Compile-time check that privateSignature implements json.Unmarshaler
|
||||
var _ json.Unmarshaler = (*privateSignature)(nil)
|
||||
// Compile-time check that untrustedSignature implements json.Unmarshaler
|
||||
var _ json.Unmarshaler = (*untrustedSignature)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (s *privateSignature) UnmarshalJSON(data []byte) error {
|
||||
func (s *untrustedSignature) UnmarshalJSON(data []byte) error {
|
||||
err := s.strictUnmarshalJSON(data)
|
||||
if err != nil {
|
||||
if _, ok := err.(jsonFormatError); ok {
|
||||
|
@ -82,7 +119,7 @@ func (s *privateSignature) UnmarshalJSON(data []byte) error {
|
|||
|
||||
// strictUnmarshalJSON is UnmarshalJSON, except that it may return the internal jsonFormatError error type.
|
||||
// Splitting it into a separate function allows us to do the jsonFormatError → InvalidSignatureError in a single place, the caller.
|
||||
func (s *privateSignature) strictUnmarshalJSON(data []byte) error {
|
||||
func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
||||
var untyped interface{}
|
||||
if err := json.Unmarshal(data, &untyped); err != nil {
|
||||
return err
|
||||
|
@ -107,7 +144,20 @@ func (s *privateSignature) strictUnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = optional // We don't use anything from here for now.
|
||||
if _, ok := optional["creator"]; ok {
|
||||
creatorID, err := stringField(optional, "creator")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedCreatorID = &creatorID
|
||||
}
|
||||
if _, ok := optional["timestamp"]; ok {
|
||||
timestamp, err := int64Field(optional, "timestamp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedTimestamp = ×tamp
|
||||
}
|
||||
|
||||
t, err := stringField(c, "type")
|
||||
if err != nil {
|
||||
|
@ -128,7 +178,7 @@ func (s *privateSignature) strictUnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.DockerManifestDigest = digest.Digest(digestString)
|
||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||
|
||||
identity, err := mapField(c, "identity")
|
||||
if err != nil {
|
||||
|
@ -141,13 +191,18 @@ func (s *privateSignature) strictUnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.DockerReference = reference
|
||||
s.UntrustedDockerReference = reference
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign formats the signature and returns a blob signed using mech and keyIdentity
|
||||
func (s privateSignature) sign(mech SigningMechanism, keyIdentity string) ([]byte, error) {
|
||||
// (If it seems surprising that this is a method on untrustedSignature, note that there
|
||||
// isn’t a good reason to think that a key used by the user is trusted by any component
|
||||
// of the system just because it is a private key — actually the presence of a private key
|
||||
// on the system increases the likelihood of an a successful attack on that private key
|
||||
// on that particular system.)
|
||||
func (s untrustedSignature) sign(mech SigningMechanism, keyIdentity string) ([]byte, error) {
|
||||
json, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -159,7 +214,7 @@ func (s privateSignature) sign(mech SigningMechanism, keyIdentity string) ([]byt
|
|||
// signatureAcceptanceRules specifies how to decide whether an untrusted signature is acceptable.
|
||||
// We centralize the actual parsing and data extraction in verifyAndExtractSignature; this supplies
|
||||
// the policy. We use an object instead of supplying func parameters to verifyAndExtractSignature
|
||||
// because all of the functions have the same type, so there is a risk of exchanging the functions;
|
||||
// because the functions have the same or similar types, so there is a risk of exchanging the functions;
|
||||
// named members of this struct are more explicit.
|
||||
type signatureAcceptanceRules struct {
|
||||
validateKeyIdentity func(string) error
|
||||
|
@ -178,16 +233,58 @@ func verifyAndExtractSignature(mech SigningMechanism, unverifiedSignature []byte
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var unmatchedSignature privateSignature
|
||||
var unmatchedSignature untrustedSignature
|
||||
if err := json.Unmarshal(signed, &unmatchedSignature); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
}
|
||||
if err := rules.validateSignedDockerManifestDigest(unmatchedSignature.DockerManifestDigest); err != nil {
|
||||
if err := rules.validateSignedDockerManifestDigest(unmatchedSignature.UntrustedDockerManifestDigest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rules.validateSignedDockerReference(unmatchedSignature.DockerReference); err != nil {
|
||||
if err := rules.validateSignedDockerReference(unmatchedSignature.UntrustedDockerReference); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature := unmatchedSignature.Signature // Policy OK.
|
||||
return &signature, nil
|
||||
// signatureAcceptanceRules have accepted this value.
|
||||
return &Signature{
|
||||
DockerManifestDigest: unmatchedSignature.UntrustedDockerManifestDigest,
|
||||
DockerReference: unmatchedSignature.UntrustedDockerReference,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUntrustedSignatureInformationWithoutVerifying extracts information available in an untrusted signature,
|
||||
// WITHOUT doing any cryptographic verification.
|
||||
// This may be useful when debugging signature verification failures,
|
||||
// or when managing a set of signatures on a single image.
|
||||
//
|
||||
// WARNING: Do not use the contents of this for ANY security decisions,
|
||||
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
|
||||
// There is NO REASON to expect the values to be correct, or not intentionally misleading
|
||||
// (including things like “✅ Verified by $authority”)
|
||||
func GetUntrustedSignatureInformationWithoutVerifying(untrustedSignatureBytes []byte) (*UntrustedSignatureInformation, error) {
|
||||
// NOTE: This should eventualy do format autodetection.
|
||||
mech, err := NewGPGSigningMechanism()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
untrustedContents, shortKeyIdentifier, err := mech.UntrustedSignatureContents(untrustedSignatureBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var untrustedDecodedContents untrustedSignature
|
||||
if err := json.Unmarshal(untrustedContents, &untrustedDecodedContents); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
}
|
||||
|
||||
var timestamp *time.Time // = nil
|
||||
if untrustedDecodedContents.UntrustedTimestamp != nil {
|
||||
ts := time.Unix(*untrustedDecodedContents.UntrustedTimestamp, 0)
|
||||
timestamp = &ts
|
||||
}
|
||||
return &UntrustedSignatureInformation{
|
||||
UntrustedDockerManifestDigest: untrustedDecodedContents.UntrustedDockerManifestDigest,
|
||||
UntrustedDockerReference: untrustedDecodedContents.UntrustedDockerReference,
|
||||
UntrustedCreatorID: untrustedDecodedContents.UntrustedCreatorID,
|
||||
UntrustedTimestamp: timestamp,
|
||||
UntrustedShortKeyIdentifier: shortKeyIdentifier,
|
||||
}, nil
|
||||
}
|
||||
|
|
377
vendor/github.com/containers/image/signature/signature_test.go
generated
vendored
Normal file
377
vendor/github.com/containers/image/signature/signature_test.go
generated
vendored
Normal file
|
@ -0,0 +1,377 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/version"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"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 TestNewUntrustedSignature(t *testing.T) {
|
||||
timeBefore := time.Now()
|
||||
sig := newUntrustedSignature(TestImageManifestDigest, TestImageSignatureReference)
|
||||
assert.Equal(t, TestImageManifestDigest, sig.UntrustedDockerManifestDigest)
|
||||
assert.Equal(t, TestImageSignatureReference, sig.UntrustedDockerReference)
|
||||
require.NotNil(t, sig.UntrustedCreatorID)
|
||||
assert.Equal(t, "atomic "+version.Version, *sig.UntrustedCreatorID)
|
||||
require.NotNil(t, sig.UntrustedTimestamp)
|
||||
timeAfter := time.Now()
|
||||
assert.True(t, timeBefore.Unix() <= *sig.UntrustedTimestamp)
|
||||
assert.True(t, *sig.UntrustedTimestamp <= timeAfter.Unix())
|
||||
}
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
// Empty string values
|
||||
s := newUntrustedSignature("", "_")
|
||||
_, err := s.MarshalJSON()
|
||||
assert.Error(t, err)
|
||||
s = newUntrustedSignature("_", "")
|
||||
_, err = s.MarshalJSON()
|
||||
assert.Error(t, err)
|
||||
|
||||
// Success
|
||||
// Use intermediate variables for these values so that we can take their addresses.
|
||||
creatorID := "CREATOR"
|
||||
timestamp := int64(1484683104)
|
||||
for _, c := range []struct {
|
||||
input untrustedSignature
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
untrustedSignature{
|
||||
UntrustedDockerManifestDigest: "digest!@#",
|
||||
UntrustedDockerReference: "reference#@!",
|
||||
UntrustedCreatorID: &creatorID,
|
||||
UntrustedTimestamp: ×tamp,
|
||||
},
|
||||
"{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{\"creator\":\"CREATOR\",\"timestamp\":1484683104}}",
|
||||
},
|
||||
{
|
||||
untrustedSignature{
|
||||
UntrustedDockerManifestDigest: "digest!@#",
|
||||
UntrustedDockerReference: "reference#@!",
|
||||
},
|
||||
"{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{}}",
|
||||
},
|
||||
} {
|
||||
marshaled, err := c.input.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte(c.expected), marshaled)
|
||||
|
||||
// Also call MarshalJSON through the JSON package.
|
||||
marshaled, err = json.Marshal(c.input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte(c.expected), marshaled)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the result of modifying validJSON with fn and unmarshaling it into *sig
|
||||
func tryUnmarshalModifiedSignature(t *testing.T, sig *untrustedSignature, 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 = untrustedSignature{}
|
||||
return json.Unmarshal(testJSON, sig)
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
var s untrustedSignature
|
||||
// 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 := newUntrustedSignature("digest!@#", "reference#@!")
|
||||
validJSON, err := validSig.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Success
|
||||
s = untrustedSignature{}
|
||||
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 },
|
||||
// Invalid "creator"
|
||||
func(v mSI) { x(v, "optional")["creator"] = 1 },
|
||||
// Invalid "timestamp"
|
||||
func(v mSI) { x(v, "optional")["timestamp"] = "unexpected" },
|
||||
}
|
||||
for _, fn := range breakFns {
|
||||
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// Modifications to unrecognized fields in "optional" are allowed and ignored
|
||||
allowedModificationFns := []func(mSI){
|
||||
// Add an optional field
|
||||
func(v mSI) { x(v, "optional")["unexpected"] = 1 },
|
||||
}
|
||||
for _, fn := range allowedModificationFns {
|
||||
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, validSig, s)
|
||||
}
|
||||
|
||||
// Optional fields can be missing
|
||||
validSig = untrustedSignature{
|
||||
UntrustedDockerManifestDigest: "digest!@#",
|
||||
UntrustedDockerReference: "reference#@!",
|
||||
UntrustedCreatorID: nil,
|
||||
UntrustedTimestamp: nil,
|
||||
}
|
||||
validJSON, err = validSig.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
s = untrustedSignature{}
|
||||
err = json.Unmarshal(validJSON, &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, validSig, s)
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := newUntrustedSignature("digest!@#", "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 errors.Errorf("Unexpected keyIdentity")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerReference: func(signedDockerReference string) error {
|
||||
if signedDockerReference != sig.UntrustedDockerReference {
|
||||
return errors.Errorf("Unexpected signedDockerReference")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
|
||||
if signedDockerManifestDigest != sig.UntrustedDockerManifestDigest {
|
||||
return errors.Errorf("Unexpected signedDockerManifestDigest")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, sig.UntrustedDockerManifestDigest, verified.DockerManifestDigest)
|
||||
assert.Equal(t, sig.UntrustedDockerReference, verified.DockerReference)
|
||||
|
||||
// Error creating blob to sign
|
||||
_, err = untrustedSignature{}.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 string
|
||||
signedDockerReference string
|
||||
signedDockerManifestDigest digest.Digest
|
||||
}
|
||||
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 errors.Errorf("keyIdentity mismatch")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerReference: func(signedDockerReference string) error {
|
||||
recorded.signedDockerReference = signedDockerReference
|
||||
if signedDockerReference != wanted.signedDockerReference {
|
||||
return errors.Errorf("signedDockerReference mismatch")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
|
||||
recorded.signedDockerManifestDigest = signedDockerManifestDigest
|
||||
if signedDockerManifestDigest != wanted.signedDockerManifestDigest {
|
||||
return errors.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)
|
||||
}
|
||||
|
||||
func TestGetUntrustedSignatureInformationWithoutVerifying(t *testing.T) {
|
||||
signature, err := ioutil.ReadFile("./fixtures/image.signature")
|
||||
require.NoError(t, err)
|
||||
// Successful parsing, all optional fields present
|
||||
info, err := GetUntrustedSignatureInformationWithoutVerifying(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, TestImageSignatureReference, info.UntrustedDockerReference)
|
||||
assert.Equal(t, TestImageManifestDigest, info.UntrustedDockerManifestDigest)
|
||||
assert.NotNil(t, info.UntrustedCreatorID)
|
||||
assert.Equal(t, "atomic ", *info.UntrustedCreatorID)
|
||||
assert.NotNil(t, info.UntrustedTimestamp)
|
||||
assert.Equal(t, time.Unix(1458239713, 0), *info.UntrustedTimestamp)
|
||||
assert.Equal(t, TestKeyShortID, info.UntrustedShortKeyIdentifier)
|
||||
// Successful parsing, no optional fields present
|
||||
signature, err = ioutil.ReadFile("./fixtures/no-optional-fields.signature")
|
||||
require.NoError(t, err)
|
||||
// Successful parsing
|
||||
info, err = GetUntrustedSignatureInformationWithoutVerifying(signature)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, TestImageSignatureReference, info.UntrustedDockerReference)
|
||||
assert.Equal(t, TestImageManifestDigest, info.UntrustedDockerManifestDigest)
|
||||
assert.Nil(t, info.UntrustedCreatorID)
|
||||
assert.Nil(t, info.UntrustedTimestamp)
|
||||
assert.Equal(t, TestKeyShortID, info.UntrustedShortKeyIdentifier)
|
||||
|
||||
// Completely invalid signature.
|
||||
_, err = GetUntrustedSignatureInformationWithoutVerifying([]byte{})
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = GetUntrustedSignatureInformationWithoutVerifying([]byte("invalid signature"))
|
||||
assert.Error(t, err)
|
||||
|
||||
// Valid signature of non-JSON
|
||||
invalidBlobSignature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
|
||||
require.NoError(t, err)
|
||||
_, err = GetUntrustedSignatureInformationWithoutVerifying(invalidBlobSignature)
|
||||
assert.Error(t, err)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue