Skip to content

Commit add9c14

Browse files
committed
Add images signatures verification with containers/image
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
1 parent eca395d commit add9c14

107 files changed

Lines changed: 17126 additions & 11 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ RUN apt-get update && apt-get install -y \
9393
python-mock \
9494
python-pip \
9595
zip \
96+
gpgme-devel \
97+
libassuan-devel \
9698
&& pip install awscli==1.10.15
9799
# Get lvm2 source for compiling statically
98100
ENV LVM2_VERSION 2.02.103
@@ -257,6 +259,20 @@ COPY hack/dockerfile/binaries-commits /tmp/binaries-commits
257259
COPY hack/dockerfile/install-binaries.sh /tmp/install-binaries.sh
258260
RUN /tmp/install-binaries.sh tomlv vndr runc containerd tini proxy bindata
259261

262+
# Install skopeo
263+
ENV SKOPEO_COMMIT v0.1.16
264+
RUN set -x \
265+
&& export GOPATH="$(mktemp -d)" \
266+
&& git clone https://github.com/projectatomic/skopeo.git "$GOPATH/src/github.com/projectatomic/skopeo" \
267+
&& cd "$GOPATH/src/github.com/projectatomic/skopeo" \
268+
&& git checkout -q "$SKOPEO_COMMIT" \
269+
&& make binary-local \
270+
&& cp skopeo /usr/local/bin/skopeo \
271+
&& mkdir -p /var/lib/atomic/sigstore \
272+
&& mkdir -p /etc/containers/registries.d \
273+
&& cp default-policy.json /etc/containers/policy.json \
274+
&& rm -rf "$GOPATH"
275+
260276
# Wrap all commands in the "docker-in-docker" script to allow nested containers
261277
ENTRYPOINT ["hack/dind"]
262278

daemon/config_unix.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Config struct {
3636
Init bool `json:"init,omitempty"`
3737
InitPath string `json:"init-path,omitempty"`
3838
SeccompProfile string `json:"seccomp-profile,omitempty"`
39+
SigCheck bool `json:"signature-verification"`
3940
}
4041

4142
// bridgeConfig stores all the bridge driver specific
@@ -89,6 +90,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
8990
flags.Int64Var(&config.CPURealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
9091
flags.Int64Var(&config.CPURealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
9192
flags.StringVar(&config.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile")
93+
flags.BoolVar(&config.SigCheck, "signature-verification", true, "Check image's signatures on pull")
9294

9395
config.attachExperimentalFlags(flags)
9496
}

daemon/image_pull.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
9292
},
9393
DownloadManager: daemon.downloadManager,
9494
Schema2Types: distribution.ImageTypes,
95+
SignatureCheck: daemon.configStore.SigCheck,
9596
}
9697

9798
err := distribution.Pull(ctx, ref, imagePullConfig)

distribution/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type ImagePullConfig struct {
5858
// Schema2Types is the valid schema2 configuration types allowed
5959
// by the pull operation.
6060
Schema2Types []string
61+
// SignatureCheck controls whether to check image's signatures or not
62+
SignatureCheck bool
6163
}
6264

6365
// ImagePushConfig stores push configuration.

distribution/pull.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
"github.com/Sirupsen/logrus"
8+
"github.com/containers/image/signature"
89
"github.com/docker/distribution/digest"
910
"github.com/docker/docker/api"
1011
"github.com/docker/docker/distribution/metadata"
@@ -28,13 +29,22 @@ type Puller interface {
2829
// through to the underlying puller implementation for use during the actual
2930
// pull operation.
3031
func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) {
32+
var pc *signature.PolicyContext
33+
if imagePullConfig.SignatureCheck {
34+
var err error
35+
pc, err = configurePolicyContext()
36+
if err != nil {
37+
return nil, err
38+
}
39+
}
3140
switch endpoint.Version {
3241
case registry.APIVersion2:
3342
return &v2Puller{
3443
V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
3544
endpoint: endpoint,
3645
config: imagePullConfig,
3746
repoInfo: repoInfo,
47+
policyContext: pc,
3848
}, nil
3949
case registry.APIVersion1:
4050
return &v1Puller{
@@ -68,15 +78,13 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
6878
fqr, err := reference.QualifyUnqualifiedReference(ref, r)
6979
if err != nil {
7080
errStr := fmt.Sprintf("Failed to fully qualify %q name with %q registry: %v", ref.Name(), r, err)
71-
progress.Message(imagePullConfig.ProgressOutput, "", errStr)
7281
if i == len(registry.DefaultRegistries)-1 {
7382
return fmt.Errorf(errStr)
7483
}
7584
continue
7685
}
7786
if err := pullFromRegistry(ctx, fqr, imagePullConfig); err != nil {
7887
// make sure we get a final "Error response from daemon: "
79-
progress.Message(imagePullConfig.ProgressOutput, "", err.Error())
8088
if i == len(registry.DefaultRegistries)-1 {
8189
return err
8290
}

distribution/pull_v2.go

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"runtime"
1212

1313
"github.com/Sirupsen/logrus"
14+
"github.com/containers/image/signature"
1415
"github.com/docker/distribution"
1516
"github.com/docker/distribution/digest"
1617
"github.com/docker/distribution/manifest/manifestlist"
@@ -57,6 +58,9 @@ type v2Puller struct {
5758
// confirmedV2 is set to true if we confirm we're talking to a v2
5859
// registry. This is used to limit fallbacks to the v1 protocol.
5960
confirmedV2 bool
61+
62+
policyContext *signature.PolicyContext
63+
originalRef reference.Named
6064
}
6165

6266
func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
@@ -91,16 +95,26 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
9195
func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
9296
var layersDownloaded bool
9397
if !reference.IsNameOnly(ref) {
98+
var err error
99+
if p.config.SignatureCheck {
100+
ref, err = p.checkTrusted(ctx, ref)
101+
if err != nil {
102+
// do not fallback to v1 is there was any error checking image's signatures
103+
return err
104+
}
105+
}
94106
layersDownloaded, err = p.pullV2Tag(ctx, ref)
95107
if err != nil {
96108
return err
97109
}
98110
} else {
99111
tags, err := p.repo.Tags(ctx).All(ctx)
100112
if err != nil {
101-
// If this repository doesn't exist on V2, we should
102-
// permit a fallback to V1.
103-
return allowV1Fallback(err)
113+
if p.config.SignatureCheck {
114+
return err
115+
} else {
116+
return allowV1Fallback(err)
117+
}
104118
}
105119

106120
// The v2 registry knows about this repository, so we will not
@@ -113,7 +127,17 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
113127
if err != nil {
114128
return err
115129
}
116-
pulledNew, err := p.pullV2Tag(ctx, tagRef)
130+
var ref reference.Named
131+
ref = tagRef
132+
if p.config.SignatureCheck {
133+
trustedRef, err := p.checkTrusted(ctx, tagRef)
134+
if err != nil {
135+
p.originalRef = nil
136+
return err
137+
}
138+
ref = trustedRef
139+
}
140+
pulledNew, err := p.pullV2Tag(ctx, ref)
117141
if err != nil {
118142
// Since this is the pull-all-tags case, don't
119143
// allow an error pulling a particular tag to
@@ -129,7 +153,11 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
129153
}
130154
}
131155

132-
writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded)
156+
if p.originalRef != nil {
157+
writeStatus(p.originalRef.String(), p.config.ProgressOutput, layersDownloaded)
158+
} else {
159+
writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded)
160+
}
133161

134162
return nil
135163
}
@@ -341,7 +369,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
341369
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
342370
manifest, err = manSvc.Get(ctx, "", distribution.WithTag(tagged.Tag()))
343371
if err != nil {
344-
return false, allowV1Fallback(err)
372+
if p.config.SignatureCheck {
373+
return false, err
374+
} else {
375+
return false, allowV1Fallback(err)
376+
}
345377
}
346378
tagOrDigest = tagged.Tag()
347379
} else if digested, isDigested := ref.(reference.Canonical); isDigested {
@@ -416,6 +448,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
416448
oldTagID, err := p.config.ReferenceStore.Get(ref)
417449
if err == nil {
418450
if oldTagID == id {
451+
if p.config.SignatureCheck {
452+
if err := p.addTrustedTag(id); err != nil {
453+
return false, err
454+
}
455+
}
419456
return false, addDigestReference(p.config.ReferenceStore, ref, manifestDigest, id)
420457
}
421458
} else if err != reference.ErrDoesNotExist {
@@ -426,6 +463,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
426463
if err = p.config.ReferenceStore.AddDigest(canonical, id, true); err != nil {
427464
return false, err
428465
}
466+
if p.config.SignatureCheck {
467+
if err := p.addTrustedTag(id); err != nil {
468+
return false, err
469+
}
470+
}
429471
} else {
430472
if err = addDigestReference(p.config.ReferenceStore, ref, manifestDigest, id); err != nil {
431473
return false, err
@@ -438,6 +480,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
438480
return true, nil
439481
}
440482

483+
func (p *v2Puller) addTrustedTag(id digest.Digest) error {
484+
if p.policyContext != nil {
485+
if _, ok := p.originalRef.(reference.Canonical); !ok {
486+
if err := p.config.ReferenceStore.AddTag(p.originalRef, id, true); err != nil {
487+
return err
488+
}
489+
}
490+
}
491+
return nil
492+
}
493+
441494
func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
442495
var verifiedManifest *schema1.Manifest
443496
verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)

distribution/pull_v2_unix.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,89 @@
33
package distribution
44

55
import (
6+
"fmt"
7+
8+
"github.com/containers/image/docker"
9+
containersImageRef "github.com/containers/image/docker/reference"
10+
"github.com/containers/image/manifest"
11+
"github.com/containers/image/signature"
12+
"github.com/containers/image/types"
613
"github.com/docker/distribution"
714
"github.com/docker/distribution/context"
15+
"github.com/docker/distribution/digest"
16+
"github.com/docker/docker/dockerversion"
17+
"github.com/docker/docker/reference"
18+
"github.com/docker/docker/registry"
19+
gctx "golang.org/x/net/context"
820
)
921

1022
func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekCloser, error) {
1123
blobs := ld.repo.Blobs(ctx)
1224
return blobs.Open(ctx, ld.digest)
1325
}
26+
27+
func configurePolicyContext() (*signature.PolicyContext, error) {
28+
defaultPolicy, err := signature.DefaultPolicy(nil)
29+
if err != nil {
30+
return nil, err
31+
}
32+
pc, err := signature.NewPolicyContext(defaultPolicy)
33+
if err != nil {
34+
return nil, err
35+
}
36+
return pc, nil
37+
}
38+
39+
func (p *v2Puller) checkTrusted(c gctx.Context, ref reference.Named) (reference.Named, error) {
40+
p.originalRef = ref
41+
// we can't use upstream docker/docker/reference since in projectatomic/docker
42+
// we modified docker/docker/reference and it's not doing any normalization.
43+
// we instead forked docker/docker/reference in containers/image and we need
44+
// this parsing here to make sure signature naming checks are consistent.
45+
dockerRef, err := containersImageRef.ParseNamed(ref.String())
46+
if err != nil {
47+
return nil, err
48+
}
49+
imgRef, err := docker.NewReference(dockerRef)
50+
if err != nil {
51+
return nil, err
52+
}
53+
isSecure := (p.endpoint.TLSConfig == nil || !p.endpoint.TLSConfig.InsecureSkipVerify)
54+
authConfig := registry.ResolveAuthConfig(p.config.AuthConfigs, p.repoInfo.Index)
55+
dockerAuthConfig := types.DockerAuthConfig{
56+
Username: authConfig.Username,
57+
Password: authConfig.Password,
58+
}
59+
ctx := &types.SystemContext{
60+
DockerInsecureSkipTLSVerify: !isSecure,
61+
DockerAuthConfig: &dockerAuthConfig,
62+
DockerRegistryUserAgent: dockerversion.DockerUserAgent(c),
63+
}
64+
img, err := imgRef.NewImage(ctx)
65+
if err != nil {
66+
return nil, err
67+
}
68+
allowed, err := p.policyContext.IsRunningImageAllowed(img)
69+
if !allowed {
70+
if err != nil {
71+
return nil, fmt.Errorf("%s isn't allowed: %v", ref.String(), err)
72+
}
73+
return nil, fmt.Errorf("%s isn't allowed", ref.String())
74+
}
75+
if err != nil {
76+
return nil, err
77+
}
78+
mfst, _, err := img.Manifest()
79+
if err != nil {
80+
return nil, err
81+
}
82+
dgst, err := manifest.Digest(mfst)
83+
if err != nil {
84+
return nil, err
85+
}
86+
ref, err = reference.WithDigest(ref, digest.Digest(dgst))
87+
if err != nil {
88+
return nil, err
89+
}
90+
return ref, nil
91+
}

distribution/pull_v2_windows.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77
"os"
88

99
"github.com/Sirupsen/logrus"
10+
"github.com/containers/image/signature"
1011
"github.com/docker/distribution"
1112
"github.com/docker/distribution/context"
1213
"github.com/docker/distribution/manifest/schema2"
1314
"github.com/docker/distribution/registry/client/transport"
15+
"github.com/docker/docker/reference"
16+
gctx "golang.org/x/net/context"
1417
)
1518

1619
var _ distribution.Describable = &v2LayerDescriptor{}
@@ -47,3 +50,11 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
4750
}
4851
return rsc, err
4952
}
53+
54+
func configurePolicyContext() (*signature.PolicyContext, error) {
55+
return nil, nil
56+
}
57+
58+
func (p *v2Puller) checkTrusted(c gctx.Context, ref reference.Named) (reference.Named, error) {
59+
return ref, nil
60+
}

hack/make.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ install_binary() {
266266
fi
267267
}
268268

269+
install_policy() {
270+
file="$1"
271+
target="/etc/containers/policy.json"
272+
if [ "$(go env GOOS)" == "linux" ]; then
273+
if [ ! -e "/etc/containers/policy.json" ]; then
274+
echo "Installing $(basename $file) to ${target}"
275+
mkdir -p /etc/containers
276+
cp -L "$file" "$target"
277+
fi
278+
fi
279+
}
280+
269281
main() {
270282
# We want this to fail if the bundles already exist and cannot be removed.
271283
# This is to avoid mixing bundles from different versions of the code.

hack/make/.integration-daemon-start

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ if [ "$(go env GOOS)" = 'windows' ]; then
1616
return
1717
fi
1818

19+
install_policy "$base/../../policy.json"
20+
1921
if [ -z "$DOCKER_TEST_HOST" ]; then
2022
if docker version &> /dev/null; then
2123
echo >&2 'skipping daemon start, since daemon appears to be already started'

0 commit comments

Comments
 (0)