Unmarshal signed payload when pulling by digest
Add a unit test for validateManifest which ensures extra data can't be injected by adding data to the JSON object outside the payload area. This also removes validation of legacy signatures at pull time. This starts the path of deprecating legacy signatures, whose presence in the very JSON document they attempt to sign is problematic. These signatures were only checked for official images, and since they only caused a weakly-worded message to be printed, removing the verification should not cause impact. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
504e67b867
commit
011bfd666e
7 changed files with 275 additions and 76 deletions
38
graph/fixtures/validate_manifest/bad_manifest
Normal file
38
graph/fixtures/validate_manifest/bad_manifest
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"schemaVersion": 2,
|
||||
"name": "library/hello-world",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"header": {
|
||||
"jwk": {
|
||||
"crv": "P-256",
|
||||
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
|
||||
"kty": "EC",
|
||||
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
|
||||
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
|
||||
},
|
||||
"alg": "ES256"
|
||||
},
|
||||
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
|
||||
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
|
||||
}
|
||||
]
|
||||
}
|
46
graph/fixtures/validate_manifest/extra_data_manifest
Normal file
46
graph/fixtures/validate_manifest/extra_data_manifest
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "library/hello-world",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
|
||||
}
|
||||
],
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:ffff95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:ffff658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"header": {
|
||||
"jwk": {
|
||||
"crv": "P-256",
|
||||
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
|
||||
"kty": "EC",
|
||||
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
|
||||
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
|
||||
},
|
||||
"alg": "ES256"
|
||||
},
|
||||
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
|
||||
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
|
||||
}
|
||||
]
|
||||
}
|
38
graph/fixtures/validate_manifest/good_manifest
Normal file
38
graph/fixtures/validate_manifest/good_manifest
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "library/hello-world",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"header": {
|
||||
"jwk": {
|
||||
"crv": "P-256",
|
||||
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
|
||||
"kty": "EC",
|
||||
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
|
||||
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
|
||||
},
|
||||
"alg": "ES256"
|
||||
},
|
||||
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
|
||||
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
|
||||
}
|
||||
]
|
||||
}
|
22
graph/fixtures/validate_manifest/no_signature_manifest
Normal file
22
graph/fixtures/validate_manifest/no_signature_manifest
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "library/hello-world",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
|
||||
}
|
||||
]
|
||||
}
|
108
graph/pull_v2.go
108
graph/pull_v2.go
|
@ -1,6 +1,7 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -18,9 +19,7 @@ import (
|
|||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/trust"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/libtrust"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -231,7 +230,7 @@ func (p *v2Puller) download(di *downloadInfo) {
|
|||
di.err <- nil
|
||||
}
|
||||
|
||||
func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bool, err error) {
|
||||
func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (tagUpdated bool, err error) {
|
||||
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
|
||||
|
||||
manSvc, err := p.repo.Manifests(context.Background())
|
||||
|
@ -239,25 +238,26 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
return false, err
|
||||
}
|
||||
|
||||
manifest, err := manSvc.GetByTag(tag)
|
||||
unverifiedManifest, err := manSvc.GetByTag(tag)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
verified, err = p.validateManifest(manifest, tag)
|
||||
if unverifiedManifest == nil {
|
||||
return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
|
||||
}
|
||||
var verifiedManifest *manifest.Manifest
|
||||
verifiedManifest, err = verifyManifest(unverifiedManifest, tag)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if verified {
|
||||
logrus.Printf("Image manifest for %s has been verified", taggedName)
|
||||
}
|
||||
|
||||
// remove duplicate layers and check parent chain validity
|
||||
err = fixManifestLayers(&manifest.Manifest)
|
||||
err = fixManifestLayers(verifiedManifest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
imgs, err := p.getImageInfos(manifest.Manifest)
|
||||
imgs, err := p.getImageInfos(verifiedManifest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
}
|
||||
}()
|
||||
|
||||
for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
|
||||
for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- {
|
||||
img := imgs[i]
|
||||
|
||||
p.graph.Retain(p.sessionID, img.id)
|
||||
|
@ -307,7 +307,7 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
img: img,
|
||||
imgIndex: i,
|
||||
poolKey: "v2layer:" + img.id,
|
||||
digest: manifest.FSLayers[i].BlobSum,
|
||||
digest: verifiedManifest.FSLayers[i].BlobSum,
|
||||
// TODO: seems like this chan buffer solved hanging problem in go1.5,
|
||||
// this can indicate some deeper problem that somehow we never take
|
||||
// error from channel in loop below
|
||||
|
@ -332,7 +332,6 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
}
|
||||
}
|
||||
|
||||
var tagUpdated bool
|
||||
for _, d := range downloads {
|
||||
if err := <-d.err; err != nil {
|
||||
return false, err
|
||||
|
@ -396,7 +395,7 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
tagUpdated = true
|
||||
}
|
||||
|
||||
manifestDigest, _, err := digestFromManifest(manifest, p.repoInfo.LocalName)
|
||||
manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo.LocalName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -416,10 +415,6 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
}
|
||||
}
|
||||
|
||||
if verified && tagUpdated {
|
||||
out.Write(p.sf.FormatStatus(p.repo.Name()+":"+tag, "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
|
||||
}
|
||||
|
||||
firstID := layerIDs[len(layerIDs)-1]
|
||||
if utils.DigestReference(tag) {
|
||||
// TODO(stevvooe): Ideally, we should always set the digest so we can
|
||||
|
@ -443,84 +438,49 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (verified bo
|
|||
return tagUpdated, nil
|
||||
}
|
||||
|
||||
// verifyTrustedKeys checks the keys provided against the trust store,
|
||||
// ensuring that the provided keys are trusted for the namespace. The keys
|
||||
// provided from this method must come from the signatures provided as part of
|
||||
// the manifest JWS package, obtained from unpackSignedManifest or libtrust.
|
||||
func (p *v2Puller) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey) (verified bool, err error) {
|
||||
if namespace[0] != '/' {
|
||||
namespace = "/" + namespace
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
b, err := key.MarshalJSON()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error marshalling public key: %s", err)
|
||||
}
|
||||
// Check key has read/write permission (0x03)
|
||||
v, err := p.trustService.CheckKey(namespace, b, 0x03)
|
||||
if err != nil {
|
||||
vErr, ok := err.(trust.NotVerifiedError)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("error running key check: %s", err)
|
||||
}
|
||||
logrus.Debugf("Key check result: %v", vErr)
|
||||
}
|
||||
verified = v
|
||||
}
|
||||
|
||||
if verified {
|
||||
logrus.Debug("Key check result: verified")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (verified bool, err error) {
|
||||
func verifyManifest(signedManifest *manifest.SignedManifest, tag string) (m *manifest.Manifest, err error) {
|
||||
// If pull by digest, then verify the manifest digest. NOTE: It is
|
||||
// important to do this first, before any other content validation. If the
|
||||
// digest cannot be verified, don't even bother with those other things.
|
||||
if manifestDigest, err := digest.ParseDigest(tag); err == nil {
|
||||
verifier, err := digest.NewDigestVerifier(manifestDigest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
payload, err := m.Payload()
|
||||
payload, err := signedManifest.Payload()
|
||||
if err != nil {
|
||||
return false, err
|
||||
// If this failed, the signatures section was corrupted
|
||||
// or missing. Treat the entire manifest as the payload.
|
||||
payload = signedManifest.Raw
|
||||
}
|
||||
if _, err := verifier.Write(payload); err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
if !verifier.Verified() {
|
||||
err := fmt.Errorf("image verification failed for digest %s", manifestDigest)
|
||||
logrus.Error(err)
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var verifiedManifest manifest.Manifest
|
||||
if err = json.Unmarshal(payload, &verifiedManifest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m = &verifiedManifest
|
||||
} else {
|
||||
m = &signedManifest.Manifest
|
||||
}
|
||||
|
||||
// TODO(tiborvass): what's the usecase for having manifest == nil and err == nil ? Shouldn't be the error be "DoesNotExist" ?
|
||||
if m == nil {
|
||||
return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
|
||||
}
|
||||
if m.SchemaVersion != 1 {
|
||||
return false, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
|
||||
return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
|
||||
}
|
||||
if len(m.FSLayers) != len(m.History) {
|
||||
return false, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
|
||||
return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
|
||||
}
|
||||
if len(m.FSLayers) == 0 {
|
||||
return false, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
|
||||
return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
|
||||
}
|
||||
keys, err := manifest.Verify(m)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error verifying manifest for tag %q: %v", tag, err)
|
||||
}
|
||||
verified, err = p.verifyTrustedKeys(m.Name, keys)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error verifying manifest keys: %v", err)
|
||||
}
|
||||
return verified, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// fixManifestLayers removes repeated layers from the manifest and checks the
|
||||
|
@ -571,7 +531,7 @@ func fixManifestLayers(m *manifest.Manifest) error {
|
|||
// getImageInfos returns an imageinfo struct for every image in the manifest.
|
||||
// These objects contain both calculated strongIDs and compatibilityIDs found
|
||||
// in v1Compatibility object.
|
||||
func (p *v2Puller) getImageInfos(m manifest.Manifest) ([]contentAddressableDescriptor, error) {
|
||||
func (p *v2Puller) getImageInfos(m *manifest.Manifest) ([]contentAddressableDescriptor, error) {
|
||||
imgs := make([]contentAddressableDescriptor, len(m.FSLayers))
|
||||
|
||||
var parent digest.Digest
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -98,3 +100,95 @@ func TestFixManifestLayersBadParent(t *testing.T) {
|
|||
t.Fatalf("expected an invalid parent ID error from fixManifestLayers")
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateManifest verifies the validateManifest function
|
||||
func TestValidateManifest(t *testing.T) {
|
||||
expectedDigest := "sha256:02fee8c3220ba806531f606525eceb83f4feb654f62b207191b1c9209188dedd"
|
||||
expectedFSLayer0 := digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
// Good manifest
|
||||
|
||||
goodManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/good_manifest")
|
||||
if err != nil {
|
||||
t.Fatal("error reading fixture:", err)
|
||||
}
|
||||
|
||||
var goodSignedManifest manifest.SignedManifest
|
||||
err = json.Unmarshal(goodManifestBytes, &goodSignedManifest)
|
||||
if err != nil {
|
||||
t.Fatal("error unmarshaling manifest:", err)
|
||||
}
|
||||
|
||||
verifiedManifest, err := verifyManifest(&goodSignedManifest, expectedDigest)
|
||||
if err != nil {
|
||||
t.Fatal("validateManifest failed:", err)
|
||||
}
|
||||
|
||||
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
|
||||
t.Fatal("unexpected FSLayer in good manifest")
|
||||
}
|
||||
|
||||
// "Extra data" manifest
|
||||
|
||||
extraDataManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/extra_data_manifest")
|
||||
if err != nil {
|
||||
t.Fatal("error reading fixture:", err)
|
||||
}
|
||||
|
||||
var extraDataSignedManifest manifest.SignedManifest
|
||||
err = json.Unmarshal(extraDataManifestBytes, &extraDataSignedManifest)
|
||||
if err != nil {
|
||||
t.Fatal("error unmarshaling manifest:", err)
|
||||
}
|
||||
|
||||
verifiedManifest, err = verifyManifest(&extraDataSignedManifest, expectedDigest)
|
||||
if err != nil {
|
||||
t.Fatal("validateManifest failed:", err)
|
||||
}
|
||||
|
||||
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
|
||||
t.Fatal("unexpected FSLayer in extra data manifest")
|
||||
}
|
||||
|
||||
// Bad manifest
|
||||
|
||||
badManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/bad_manifest")
|
||||
if err != nil {
|
||||
t.Fatal("error reading fixture:", err)
|
||||
}
|
||||
|
||||
var badSignedManifest manifest.SignedManifest
|
||||
err = json.Unmarshal(badManifestBytes, &badSignedManifest)
|
||||
if err != nil {
|
||||
t.Fatal("error unmarshaling manifest:", err)
|
||||
}
|
||||
|
||||
verifiedManifest, err = verifyManifest(&badSignedManifest, expectedDigest)
|
||||
if err == nil || !strings.HasPrefix(err.Error(), "image verification failed for digest") {
|
||||
t.Fatal("expected validateManifest to fail with digest error")
|
||||
}
|
||||
|
||||
// Manifest with no signature
|
||||
|
||||
expectedWholeFileDigest := "7ec3615a120efcdfc270e9c7ea4183330775a3e52a09e2efb194b9a7c18e5ff7"
|
||||
|
||||
noSignatureManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/no_signature_manifest")
|
||||
if err != nil {
|
||||
t.Fatal("error reading fixture:", err)
|
||||
}
|
||||
|
||||
var noSignatureSignedManifest manifest.SignedManifest
|
||||
err = json.Unmarshal(noSignatureManifestBytes, &noSignatureSignedManifest)
|
||||
if err != nil {
|
||||
t.Fatal("error unmarshaling manifest:", err)
|
||||
}
|
||||
|
||||
verifiedManifest, err = verifyManifest(&noSignatureSignedManifest, expectedWholeFileDigest)
|
||||
if err != nil {
|
||||
t.Fatal("validateManifest failed:", err)
|
||||
}
|
||||
|
||||
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
|
||||
t.Fatal("unexpected FSLayer in no-signature manifest")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,8 +102,9 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
|
|||
func digestFromManifest(m *manifest.SignedManifest, localName string) (digest.Digest, int, error) {
|
||||
payload, err := m.Payload()
|
||||
if err != nil {
|
||||
logrus.Debugf("could not retrieve manifest payload: %v", err)
|
||||
return "", 0, err
|
||||
// If this failed, the signatures section was corrupted
|
||||
// or missing. Treat the entire manifest as the payload.
|
||||
payload = m.Raw
|
||||
}
|
||||
manifestDigest, err := digest.FromBytes(payload)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue