mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-27 12:15:33 +00:00
1af303239b
This adds a HASH file next to the VERSION file in the image context folders for prebuilt docker images. And uses the HASH for referencing the image in the tasks created by the decision task. This way docker will validate the image hash when pulling it in production. Thus, attackers won't be able to inject code by compromising the remote docker registries we use to store prebuilt images. Further more, this makes validation of the Chain-Of-Trust artifacts easier as this eliminates the need for whitelists and hash validation. MozReview-Commit-ID: FD3B9MyeU9Q --HG-- extra : rebase_source : e01cdbd0db06b36ba95dec3da936ee307a23aae7
213 lines
8.1 KiB
Python
213 lines
8.1 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
import os
|
|
import shutil
|
|
import stat
|
|
import tarfile
|
|
import tempfile
|
|
import unittest
|
|
|
|
from ..util import docker
|
|
from mozunit import MockedOpen
|
|
|
|
|
|
MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
|
|
|
|
|
class TestDocker(unittest.TestCase):
|
|
|
|
def test_generate_context_hash(self):
|
|
tmpdir = tempfile.mkdtemp()
|
|
old_GECKO = docker.GECKO
|
|
docker.GECKO = tmpdir
|
|
try:
|
|
os.makedirs(os.path.join(tmpdir, 'docker', 'my-image'))
|
|
p = os.path.join(tmpdir, 'docker', 'my-image', 'Dockerfile')
|
|
with open(p, 'w') as f:
|
|
f.write("FROM node\nADD a-file\n")
|
|
os.chmod(p, MODE_STANDARD)
|
|
p = os.path.join(tmpdir, 'docker', 'my-image', 'a-file')
|
|
with open(p, 'w') as f:
|
|
f.write("data\n")
|
|
os.chmod(p, MODE_STANDARD)
|
|
self.assertEqual(
|
|
docker.generate_context_hash(docker.GECKO,
|
|
os.path.join(docker.GECKO, 'docker/my-image'),
|
|
'my-image'),
|
|
'e61e675ce05e8c11424437db3f1004079374c1a5fe6ad6800346cebe137b0797'
|
|
)
|
|
finally:
|
|
docker.GECKO = old_GECKO
|
|
shutil.rmtree(tmpdir)
|
|
|
|
def test_docker_image_explicit_registry(self):
|
|
files = {}
|
|
files["{}/myimage/REGISTRY".format(docker.IMAGE_DIR)] = "cool-images"
|
|
files["{}/myimage/VERSION".format(docker.IMAGE_DIR)] = "1.2.3"
|
|
files["{}/myimage/HASH".format(docker.IMAGE_DIR)] = "sha256:434..."
|
|
with MockedOpen(files):
|
|
self.assertEqual(docker.docker_image('myimage'), "cool-images/myimage@sha256:434...")
|
|
|
|
def test_docker_image_explicit_registry_by_tag(self):
|
|
files = {}
|
|
files["{}/myimage/REGISTRY".format(docker.IMAGE_DIR)] = "myreg"
|
|
files["{}/myimage/VERSION".format(docker.IMAGE_DIR)] = "1.2.3"
|
|
files["{}/myimage/HASH".format(docker.IMAGE_DIR)] = "sha256:434..."
|
|
with MockedOpen(files):
|
|
self.assertEqual(docker.docker_image('myimage', by_tag=True), "myreg/myimage:1.2.3")
|
|
|
|
def test_docker_image_default_registry(self):
|
|
files = {}
|
|
files["{}/REGISTRY".format(docker.IMAGE_DIR)] = "mozilla"
|
|
files["{}/myimage/VERSION".format(docker.IMAGE_DIR)] = "1.2.3"
|
|
files["{}/myimage/HASH".format(docker.IMAGE_DIR)] = "sha256:434..."
|
|
with MockedOpen(files):
|
|
self.assertEqual(docker.docker_image('myimage'), "mozilla/myimage@sha256:434...")
|
|
|
|
def test_docker_image_default_registry_by_tag(self):
|
|
files = {}
|
|
files["{}/REGISTRY".format(docker.IMAGE_DIR)] = "mozilla"
|
|
files["{}/myimage/VERSION".format(docker.IMAGE_DIR)] = "1.2.3"
|
|
files["{}/myimage/HASH".format(docker.IMAGE_DIR)] = "sha256:434..."
|
|
with MockedOpen(files):
|
|
self.assertEqual(docker.docker_image('myimage', by_tag=True), "mozilla/myimage:1.2.3")
|
|
|
|
def test_create_context_tar_basic(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test_image')
|
|
os.mkdir(d)
|
|
with open(os.path.join(d, 'Dockerfile'), 'a'):
|
|
pass
|
|
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
|
|
|
|
with open(os.path.join(d, 'extra'), 'a'):
|
|
pass
|
|
os.chmod(os.path.join(d, 'extra'), MODE_STANDARD)
|
|
|
|
tp = os.path.join(tmp, 'tar')
|
|
h = docker.create_context_tar(tmp, d, tp, 'my_image')
|
|
self.assertEqual(h, '2a6d7f1627eba60daf85402418e041d728827d309143c6bc1c6bb3035bde6717')
|
|
|
|
# File prefix should be "my_image"
|
|
with tarfile.open(tp, 'r:gz') as tf:
|
|
self.assertEqual(tf.getnames(), [
|
|
'my_image/Dockerfile',
|
|
'my_image/extra',
|
|
])
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
def test_create_context_topsrcdir_files(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test-image')
|
|
os.mkdir(d)
|
|
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
|
|
fh.write(b'# %include extra/file0\n')
|
|
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
|
|
|
|
extra = os.path.join(tmp, 'extra')
|
|
os.mkdir(extra)
|
|
with open(os.path.join(extra, 'file0'), 'a'):
|
|
pass
|
|
os.chmod(os.path.join(extra, 'file0'), MODE_STANDARD)
|
|
|
|
tp = os.path.join(tmp, 'tar')
|
|
h = docker.create_context_tar(tmp, d, tp, 'test_image')
|
|
self.assertEqual(h, '20faeb7c134f21187b142b5fadba94ae58865dc929c6c293d8cbc0a087269338')
|
|
|
|
with tarfile.open(tp, 'r:gz') as tf:
|
|
self.assertEqual(tf.getnames(), [
|
|
'test_image/Dockerfile',
|
|
'test_image/topsrcdir/extra/file0',
|
|
])
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
def test_create_context_absolute_path(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test-image')
|
|
os.mkdir(d)
|
|
|
|
# Absolute paths in %include syntax are not allowed.
|
|
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
|
|
fh.write(b'# %include /etc/shadow\n')
|
|
|
|
with self.assertRaisesRegexp(Exception, 'cannot be absolute'):
|
|
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
def test_create_context_outside_topsrcdir(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test-image')
|
|
os.mkdir(d)
|
|
|
|
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
|
|
fh.write(b'# %include foo/../../../etc/shadow\n')
|
|
|
|
with self.assertRaisesRegexp(Exception, 'path outside topsrcdir'):
|
|
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
def test_create_context_missing_extra(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test-image')
|
|
os.mkdir(d)
|
|
|
|
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
|
|
fh.write(b'# %include does/not/exist\n')
|
|
|
|
with self.assertRaisesRegexp(Exception, 'path does not exist'):
|
|
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
|
|
finally:
|
|
shutil.rmtree(tmp)
|
|
|
|
def test_create_context_extra_directory(self):
|
|
tmp = tempfile.mkdtemp()
|
|
try:
|
|
d = os.path.join(tmp, 'test-image')
|
|
os.mkdir(d)
|
|
|
|
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
|
|
fh.write(b'# %include extra\n')
|
|
fh.write(b'# %include file0\n')
|
|
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
|
|
|
|
extra = os.path.join(tmp, 'extra')
|
|
os.mkdir(extra)
|
|
for i in range(3):
|
|
p = os.path.join(extra, 'file%d' % i)
|
|
with open(p, 'wb') as fh:
|
|
fh.write(b'file%d' % i)
|
|
os.chmod(p, MODE_STANDARD)
|
|
|
|
with open(os.path.join(tmp, 'file0'), 'a'):
|
|
pass
|
|
os.chmod(os.path.join(tmp, 'file0'), MODE_STANDARD)
|
|
|
|
tp = os.path.join(tmp, 'tar')
|
|
h = docker.create_context_tar(tmp, d, tp, 'my_image')
|
|
|
|
self.assertEqual(h, 'e5440513ab46ae4c1d056269e1c6715d5da7d4bd673719d360411e35e5b87205')
|
|
|
|
with tarfile.open(tp, 'r:gz') as tf:
|
|
self.assertEqual(tf.getnames(), [
|
|
'my_image/Dockerfile',
|
|
'my_image/topsrcdir/extra/file0',
|
|
'my_image/topsrcdir/extra/file1',
|
|
'my_image/topsrcdir/extra/file2',
|
|
'my_image/topsrcdir/file0',
|
|
])
|
|
finally:
|
|
shutil.rmtree(tmp)
|