From e893cdf008d4daf2023776691176f598f456aa14 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Oct 03 2016 16:46:26 +0000 Subject: Implement sign-container Signed-off-by: Patrick Uiterwijk --- diff --git a/ChangeLog b/ChangeLog index d693a47..43a2bda 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,9 @@ * src/server.py: Added gpg_signature for plain gpg signatures. + * Server/Bridge/Client: Implemented Container signing, using skopeo + scheme. + 2016-10-01 Patrick Uiterwijk * src/utils.py (nss_init): Check whether NSS password is correct on diff --git a/src/bridge.py b/src/bridge.py index fc2b7f0..8dda0b8 100644 --- a/src/bridge.py +++ b/src/bridge.py @@ -1016,6 +1016,9 @@ request_types = { max_payload=1024*1024*1024), 'sign-git-tag': RT((SF('key'),), max_payload=1024*1024*1024), + 'sign-container': RT((SF('key'), + SF('docker-reference')), + max_payload=1024*1024*1024), 'sign-ostree': RT((SF('key'), SF('ostree-hash')), max_payload=1024*1024*1024), diff --git a/src/client.py b/src/client.py index 765bdc3..88d79b4 100644 --- a/src/client.py +++ b/src/client.py @@ -1030,6 +1030,40 @@ def cmd_sign_git_tag(conn, args): call_git(['update-ref', 'refs/tags/%s' % args[1], signed_oid, unsigned_oid]) +def cmd_sign_container(conn, args): + p2 = optparse.OptionParser(usage='%prog sign-container [options] key manifest tag', + description='Sign a Docker container') + p2.add_option('-o', '--output', metavar='FILE', + help='Write output to this file') + (o2, args) = p2.parse_args(args) + if len(args) != 3: + p2.error('key name, manifest file and tag name expected') + if o2.output is None and sys.stdout.isatty(): + p2.error('won\'t write output to a TTY, specify a file name') + + passphrase = read_key_passphrase(conn.config) + + try: + f = open(args[1], 'r') + except IOError, e: + raise ClientError('Error opening %s: %s' % (args[1], e.strerror)) + + try: + conn.connect('sign-container', {'key': safe_string(args[0]), + 'docker-reference': safe_string(args[2])}) + conn.send_payload_from_file(f) + finally: + f.close() + conn.send_inner({'passphrase': passphrase}) + conn.read_response() + try: + if o2.output is None: + conn.write_payload_to_file(sys.stdout) + else: + utils.write_new_file(o2.output, conn.write_payload_to_file) + except IOError, e: + raise ClientError('Error writing to %s: %s' % (o2.output, e.strerror)) + def cmd_sign_ostree(conn, args): p2 = optparse.OptionParser(usage='%prog sign-ostree [options] key hash input_file', description='Sign an OSTree commit object') @@ -1424,6 +1458,7 @@ command_handlers = { 'sign-text': (cmd_sign_text, 'Output a cleartext signature of a text'), 'sign-data': (cmd_sign_data, 'Create a detached signature'), 'sign-git-tag': (cmd_sign_git_tag, 'Sign a git tag'), + 'sign-container': (cmd_sign_container, 'Sign an atomic docker container'), 'sign-ostree': (cmd_sign_ostree, 'Sign an OSTree commit object'), 'sign-rpm': (cmd_sign_rpm, 'Sign a RPM'), 'sign-rpms': (cmd_sign_rpms, 'Sign one or more RPMs'), diff --git a/src/server.py b/src/server.py index 890b0af..12102a5 100644 --- a/src/server.py +++ b/src/server.py @@ -1342,6 +1342,51 @@ def cmd_sign_ostree(db, conn): input_file.close() signature_file.close() +@request_handler(payload_storage=RequestHandler.PAYLOAD_FILE) +def cmd_sign_container(db, conn): + (access, key_passphrase) = conn.authenticate_user(db) + + checksum = hashlib.sha256(conn.payload_file.read()).hexdigest() + + docker_manifest_digest = 'sha256:%s' % checksum + docker_reference = conn.safe_outer_field('docker-reference') + + # github.com/containers/image/signature/signature.go#51 + sig_obj = { + 'critical': { + 'type': 'atomic container signature', + 'image': { + 'docker-manifest-digest': docker_manifest_digest + }, + 'identity': { + 'docker-reference': docker_reference + } + }, + 'optional': { + 'creator': 'Sigul', + 'timestamp': int(time.time()) + } + } + + try: + signature_file = tempfile.TemporaryFile() + input_file = tempfile.TemporaryFile() + input_file.write(json.dumps(sig_obj)) + input_file.seek(0) + server_common.gpg_signature(conn.config, signature_file, + input_file, + access.key.fingerprint, + key_passphrase, + armor=False) + logging.info('Signed container %s (%s) with key %s', + docker_reference, + docker_manifest_digest, + access.key.name) + conn.send_reply_header(errors.OK, {}) + conn.send_reply_payload_from_file(signature_file) + finally: + signature_file.close() + @request_handler(payload_storage=RequestHandler.PAYLOAD_FILE, payload_auth_optional=True) def cmd_sign_rpm(db, conn): diff --git a/tests/containers.at b/tests/containers.at new file mode 100644 index 0000000..580550f --- /dev/null +++ b/tests/containers.at @@ -0,0 +1,197 @@ +# Copyright (C) 2016 Red Hat, Inc. All rights reserved. +# +# This copyrighted material is made available to anyone wishing to use, modify, +# copy, or redistribute it subject to the terms and conditions of the GNU +# General Public License v.2. This program is distributed in the hope that it +# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the +# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. You should have +# received a copy of the GNU General Public License along with this program; if +# not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth +# Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are +# incorporated in the source code or documentation are not subject to the GNU +# General Public License and may only be used or replicated with the express +# permission of Red Hat, Inc. +# +# Red Hat Author: Patrick Uiterwijk + +AT_SETUP([Container check]) + +mkdir ca client bridge server gnupg rpm +chmod 700 gnupg + +AT_DATA([nss_password_file], [[nss-pw +]]) +AT_DATA([pkcs12_password_file], [[pk12-pw +]]) + +# Set up a CA and create all certificates +AT_CHECK([certutil -d ca -N -f nss_password_file]) +# Specify serial number (-m) explicitly because it is time-based by default, +# and creating certificates quickly can result in a collision. +AT_CHECK([certutil -d ca -S -f nss_password_file -z /dev/null -n my-ca \ + -s 'CN=My CA' -t CT,, -x -v 120 -m 1], , , [ignore]) +AT_CHECK([certutil -d ca -L -n my-ca -a > ca.pem]) +AT_CHECK([certutil -d ca -S -f nss_password_file -z /dev/null \ + -n sigul-bridge-cert -s 'CN=localhost,OU=bridge' -c my-ca \ + -t u,, -v 120 -m 2], , , [ignore]) +AT_CHECK([pk12util -d ca -o bridge.p12 -n sigul-bridge-cert \ + -k nss_password_file -w pkcs12_password_file], , [ignore]) +AT_CHECK([certutil -d ca -S -f nss_password_file -z /dev/null \ + -n sigul-server-cert -s 'CN=localhost,OU=server' -c my-ca \ + -t u,, -v 120 -m 3], , , [ignore]) +AT_CHECK([pk12util -d ca -o server.p12 -n sigul-server-cert \ + -k nss_password_file -w pkcs12_password_file], , [ignore]) +AT_CHECK([certutil -d ca -S -f nss_password_file -z /dev/null \ + -n sigul-client-cert -s 'CN=root' -c my-ca -t u,, -v 120 -m 4], + , , [ignore]) +AT_CHECK([pk12util -d ca -o client.p12 -n sigul-client-cert \ + -k nss_password_file -w pkcs12_password_file], , [ignore]) + + +# Set up and start bridge: +AT_CHECK([certutil -d bridge -N -f nss_password_file]) +AT_CHECK([certutil -d bridge -A -n my-ca -t CT,, -a -i ca.pem]) +AT_CHECK([pk12util -d bridge -i bridge.p12 -k nss_password_file \ + -w pkcs12_password_file], , [ignore]) +rm bridge.p12 + +[cat > bridge/bridge.conf < server/server.conf < client/client.conf < public.asc]) +AT_CHECK([gpg -q --homedir gnupg --import public.asc]) +rm public.asc + + +# sign-container +[cat > manifest.json <