From 64fe11a5f144b1fd59867bc5e0a2a9b5a08a2344 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 13 May 2016 18:29:57 -0400 Subject: [PATCH] Add ACI signing tests --- test/registry_tests.py | 96 ++++++++++++++++++++++++++----------- test/signing-private.gpg | Bin 0 -> 1309 bytes test/signing-public.gpg | 19 ++++++++ test/testconfig.py | 8 ++++ util/registry/queuefile.py | 2 +- 5 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 test/signing-private.gpg create mode 100644 test/signing-public.gpg diff --git a/test/registry_tests.py b/test/registry_tests.py index 0f7309640..a5af86700 100644 --- a/test/registry_tests.py +++ b/test/registry_tests.py @@ -7,6 +7,8 @@ import string import resumablehashlib import binascii import uuid +import time +import gpgme import Crypto.Random from cachetools import lru_cache @@ -352,6 +354,9 @@ class V1RegistryPushMixin(V1RegistryMixin): if 'size' in image_data: image_json_data['Size'] = image_data['size'] + if 'parent' in image_data: + image_json_data['parent'] = image_data['parent'] + self.conduct('PUT', '/v1/images/%s/json' % image_id, data=json.dumps(image_json_data), auth='sig') @@ -368,7 +373,7 @@ class V1RegistryPushMixin(V1RegistryMixin): # PUT /v1/repositories/{namespace}/{repository}/tags/latest - self.do_tag(namespace, repository, 'latest', images[0]['id']) + self.do_tag(namespace, repository, 'latest', images[-1]['id']) # PUT /v1/repositories/{namespace}/{repository}/images self.conduct('PUT', '/v1/repositories/%s/images' % repo_name, @@ -423,7 +428,7 @@ class V1RegistryPullMixin(V1RegistryMixin): response = self.conduct('GET', image_prefix + 'layer', auth='sig') # Ensure we can parse the layer bytes and that they contain the contents. - self.assertContents(images[index], response) + self.assertContents(images[len(images) - index - 1], response) class V2RegistryMixin(BaseRegistryMixin): @@ -529,7 +534,7 @@ class V2RegistryPushMixin(V2RegistryMixin): builder = SignedManifestBuilder(namespace, repository, tag_name) full_contents = {} - for image_data in images: + for image_data in reversed(images): full_contents[image_data['id']] = _get_full_contents(image_data, additional_fields=munge_shas) checksum = 'sha256:' + hashlib.sha256(full_contents[image_data['id']]).hexdigest() if invalid: @@ -544,7 +549,7 @@ class V2RegistryPushMixin(V2RegistryMixin): # Push the image's layers. checksums = {} - for image_data in images: + for image_data in reversed(images): image_id = image_data['id'] layer_bytes = full_contents[image_data['id']] chunks = image_data.get('chunks') @@ -662,7 +667,7 @@ class V2RegistryPullMixin(V2RegistryMixin): # Verify the layers. blobs = {} - for index, layer in enumerate(manifest_data['fsLayers']): + for index, layer in enumerate(reversed(manifest_data['fsLayers'])): blob_id = layer['blobSum'] result = self.conduct('GET', '/v2/%s/blobs/%s' % (repo_name, blob_id), expected_code=200, auth='jwt') @@ -726,15 +731,15 @@ class RegistryTestsMixin(object): return images = [ + { + 'id': 'baseid', + 'contents': 'The base image', + }, { 'id': 'latestid', 'contents': 'the latest image', 'parent': 'baseid', }, - { - 'id': 'baseid', - 'contents': 'The base image', - } ] # Push a new repository. @@ -756,16 +761,16 @@ class RegistryTestsMixin(object): return images = [ + { + 'id': 'baseid', + 'contents': 'The base image', + }, { 'id': 'latestid', 'contents': 'The latest image', 'unicode': u'the Pawe\xc5\x82 Kami\xc5\x84ski image', 'parent': 'baseid', }, - { - 'id': 'baseid', - 'contents': 'The base image', - } ] # Push a new repository. @@ -1260,15 +1265,15 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix def test_multiple_layers(self): # Push a manifest with multiple layers. images = [ + { + 'id': 'baseid', + 'contents': 'The base image', + }, { 'id': 'latestid', 'contents': 'the latest image', 'parent': 'baseid', }, - { - 'id': 'baseid', - 'contents': 'The base image', - } ] self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images) @@ -1438,10 +1443,34 @@ class ACIConversionTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerT """ Tests for registry ACI conversion. """ def get_converted_image(self): - response = self.conduct('GET', '/c1/aci/localhost:5000/devtable/newrepo/latest/aci/linux/amd64', auth='sig') + response = self.conduct('GET', '/c1/aci/localhost:5000/devtable/newrepo/latest/aci/linux/amd64/', auth='sig') tar = tarfile.open(fileobj=StringIO(response.content)) return tar, response.content + def get_converted_signature(self): + # Give time for the signature to be written before continuing. + time.sleep(1) + response = self.conduct('GET', '/c1/aci/localhost:5000/devtable/newrepo/latest/aci.asc/linux/amd64/', auth='sig') + return response.content + + def _verify_signature(self, signature, converted): + sig_bytes = StringIO(signature) + content_bytes = StringIO(converted) + + ctx = gpgme.Context() + sigs = ctx.verify(sig_bytes, content_bytes, None) + + self.assertEqual(len(sigs), 1) + self.assertEqual(sigs[0].summary, 0) + self.assertEqual(sigs[0].fpr, '07692864E17025DD1BEA88E44632047EEEB32221') + self.assertEqual(sigs[0].status, None) + self.assertEqual(sigs[0].notations, []) + self.assertEqual(sigs[0].exp_timestamp, 0) + self.assertEqual(sigs[0].wrong_key_usage, False) + self.assertEqual(sigs[0].validity, gpgme.VALIDITY_UNKNOWN) + self.assertEqual(sigs[0].validity_reason, None) + + def test_basic_conversion(self): initial_images = [ { @@ -1454,7 +1483,10 @@ class ACIConversionTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerT self.do_push('devtable', 'newrepo', 'devtable', 'password', images=initial_images) # Pull the squashed version of the tag. - tar, _ = self.get_converted_image() + tar, converted = self.get_converted_image() + signature = self.get_converted_signature() + + # Verify the manifest. self.assertEquals(['manifest', 'rootfs', 'rootfs/contents'], tar.getnames()) manifest = json.loads(tar.extractfile(tar.getmember('manifest')).read()) @@ -1488,16 +1520,19 @@ class ACIConversionTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerT self.assertEquals(manifest, expected_manifest) self.assertEquals('the initial image', tar.extractfile(tar.getmember('rootfs/contents')).read()) + # Verify the signature. + self._verify_signature(signature, converted) + def test_multilayer_conversion(self): images = [ + { + 'id': 'baseid', + 'contents': 'The base image', + }, { 'id': 'latestid', 'contents': 'the latest image', 'parent': 'baseid', - }, - { - 'id': 'baseid', - 'contents': 'The base image', } ] @@ -1505,10 +1540,15 @@ class ACIConversionTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerT self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images) # Pull the squashed version of the tag. - tar, _ = self.get_converted_image() + tar, converted = self.get_converted_image() + signature = self.get_converted_signature() + self.assertEquals(['manifest', 'rootfs', 'rootfs/contents'], tar.getnames()) self.assertEquals('the latest image', tar.extractfile(tar.getmember('rootfs/contents')).read()) + # Verify the signature. + self._verify_signature(signature, converted) + class SquashingTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerTestCase): """ Tests for registry squashing. """ @@ -1573,15 +1613,15 @@ class SquashingTests(RegistryTestCaseMixin, V1RegistryPushMixin, LiveServerTestC def test_multilayer_squashing(self): images = [ + { + 'id': 'baseid', + 'contents': 'The base image', + }, { 'id': 'latestid', 'contents': 'the latest image', 'parent': 'baseid', }, - { - 'id': 'baseid', - 'contents': 'The base image', - } ] # Create the repo. diff --git a/test/signing-private.gpg b/test/signing-private.gpg new file mode 100644 index 0000000000000000000000000000000000000000..baaf6af8fb75a1c874d4c5360c4d86594c595d61 GIT binary patch literal 1309 zcmV+&1>*XZ1DFI<)iW>w2mri|$-=DB%9BFi$&}qpTVM4Fwrs7tV?i>=k1bi})VG4+ zK@fn~hh(sobE#l$q;hAfMijyN7iMR3;qPBbEoPdChm<@slH*s$Vs#t|*Gm`;F@;&M!5y#_wmgcyxopRcfL(V7qw)+TgXmj zkQ?L>i?RI89Zg}2g+abtbTIBZgccohOd*+y+qA7`qZFdu&6cZ~Zp~`sYV{m?AkwUU z(&i`QjdOw)eMeGiXb7OuUEU|f0Btf1Wb?H3MFBvdyF0U>Ie*9ZY{pn@BuvFBB+^u` z_haUV5N?rV@qO~o=Y0SX0RRC22mB6o$vsxbm;;9+dJ-azl8awmX;K(iz?tQ3ARX92 zK|J}Iki^wIvNLp_VTobQ#>mG`b){|M;v9xvin}t=huhGK8|$!IJTI3q!AU*mJ57}U zo&t*fvME5NciB_H4Iy{$`VEUM?iNuT$}oQh{EfZjSI8v2l#WEMZl8xE&-nCmzgHFP zi_6yItxwbV$^-?|RgjY{F`)XQC+iQ`@TU5H^Qy!%Dzu{{OY|Th;@}EQ(a{iK6#DEr7k?$3T(9E6qkR9xv7sZef+nH-J{ zq;Fk7I)iVxK#+(GxPq>ACjkTi(G`CA#Fz~3-?Kav;y$YOqiauxF9_!b9+7KEqm{O1 zUB2H+fvY3o2}JjE-MyxE!pwj%oAlY4C@B2Fe{%WoKfUcjqA{0aE3x{#9HM&H*`QEfRz%os0-`zT1!?1OVpT_|6&v zSLJIB%qUvy#j)Za?7vlzysqt#ZfN&5Kd$;Nk1tWzYokK5D<$a&#s0nBlt@Yj7$fXZ z2Rw9%AX1)E^QnyU2THwT&$k1F5pC$Cjh*|zS^H!goSD>?Mtgx>e)%uG+?76l)6OfB zBHSTYr?S?85JA|lpVL_J8{Gr_93c)7g=a<-y!d;R!1bhfi^2KEjtVQy$I5$WKFOvh zX!nCs0UL*AbLt!k$~w`8oqx4oF)k`2owH8@kuD$K*;yizIk6tHbJaaj0n z$7m{)UOx8v_Rm2?z%R0s#gU2m%QT3j`Jd0|5da z0Rk6*0162ZMlu9`?z18x==2BvFd;Z?L#soqs~sc9d9m4@8gWMNkMf@TV%5$$f!v!u$ z`%xp-qG4Xh%n2k&j46Py0JVs0-1YxgZLOAU*$IJiFfIVWP=_BM%0c)z-x}%qQr$Cz T&HV>gQ&-#GPV)x*y)QxUQx{_5 literal 0 HcmV?d00001 diff --git a/test/signing-public.gpg b/test/signing-public.gpg new file mode 100644 index 000000000..e91779503 --- /dev/null +++ b/test/signing-public.gpg @@ -0,0 +1,19 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQENBFTVMzABCAC8jcnCrNHKk0LgyZTdTFtf9Qm2bK27Y0EyyI8tWefUt4LhQRCA +14dksJVzqWBtpHJnqkYUwfoXZmdz4e9fSS1mmoiHlDwzkuNXx2J1HAnXSxgNMV1D +JQmfxhKQzFTgkTEN03txPZrOMrDNIZSw0gkAbiBGuQXk9/HNGbzdjkd3vk1GF7Vk +v1vITmWQG+QQi7H8zR1NYYuFQb5cdDDuOoQWHXNMIZmK27StZ6MUot3NlquZbs1q +5Gr1HHog0qx+0uYn441zghZ9R1JqaAig0V3eJ8UAbTIMZPO09UUBQKC7O7OgOX/H +92zGWGwkTMUqJNJUr/dj5ocQbpFk8X3yz+d9ABEBAAG0RFF1YXkuaW8gQUNJIENv +bnZlcnRlciAoQUNJIGNvbnZlcnNpb24gc2lnbmluZyBrZXkpIDxzdXBwb3J0QHF1 +YXkuaW8+iQE5BBMBAgAjBQJU1TMwAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgEC +F4AACgkQRjIEfu6zIiHo9Af+MCE4bUOrQ6yrHSPHebHwSARULaTB0Rlj4BAXlv+A +nUJDaaYaYExo8SHZMWF5X4d4mh57DJOsIXMjIWNKpf9/0hpxRu+P8p77YtXOOeRS +3xFdq7cOK1yQ8h/iRoXyLaxAFgWvVH+Ttmx4DLr+NsyzEQBjADeBCcF4YR9OZ7fj +ZYsoq68hH0W7zgZTSrCgvyGxdpu+UWWk/eV/foktxKBMV8K2GmAwyOlsAm59PgUI +EhfFH0WAEx6+jsMFLkn7USPWomFeyMwJJEiMKYCwALWIbNz1/1dtrZZs2QmBcjAu +AMFQhx8fykH4ON8a6fpS3TOEzf0HV1NX295O8wb8vS9B7w== +=aImY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/testconfig.py b/test/testconfig.py index 2470e39b1..79a93a936 100644 --- a/test/testconfig.py +++ b/test/testconfig.py @@ -54,6 +54,7 @@ class TestConfig(DefaultConfig): FEATURE_GITHUB_BUILD = True FEATURE_BITTORRENT = True + FEATURE_ACI_CONVERSION = True INSTANCE_SERVICE_KEY_LOCATION = 'test/data/test.pem' @@ -65,3 +66,10 @@ class TestConfig(DefaultConfig): SECURITY_SCANNER_API_VERSION = 'v1' SECURITY_SCANNER_ENGINE_VERSION_TARGET = 1 SECURITY_SCANNER_API_TIMEOUT_SECONDS = 1 + + SIGNING_ENGINE = 'gpg2' + + GPG2_PRIVATE_KEY_NAME = 'EEB32221' + GPG2_PRIVATE_KEY_FILENAME = 'signing-private.gpg' + GPG2_PUBLIC_KEY_FILENAME = 'signing-public.gpg' + diff --git a/util/registry/queuefile.py b/util/registry/queuefile.py index 28012a852..3f0dc32ce 100644 --- a/util/registry/queuefile.py +++ b/util/registry/queuefile.py @@ -44,7 +44,7 @@ class QueueFile(object): handled = True if handled: - return + return '' else: raise result.exception