From c07e15472bc251ab5e0ca44ac6297e7b56f45923 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Jan 13 2021 15:31:41 +0000 Subject: Add support for File Signing Keys This will pass --fille-signing-key and --file-signing-key-passphrase-file to Sigul if they are configured for the tag and globally respectively. Signed-off-by: Patrick Uiterwijk --- diff --git a/robosignatory.toml b/robosignatory.toml index 51452ce..f4fadd9 100644 --- a/robosignatory.toml +++ b/robosignatory.toml @@ -85,6 +85,7 @@ handlers = ["console"] # These are arguments to the __init__ method of that helper. user = "robosignatory" passphrase_file = "robosignatory.pass" + file_signing_passphrase_file = "robosignatory-file-signing.pass" config_file = "/etc/sigul/client.conf" [consumer_config.koji_instances] @@ -103,6 +104,7 @@ handlers = ["console"] from = 'rawhide-signcandidate' to = 'rawhide' key = 'fedora26' + file_signing_key = 'fedora-file-signing' keyid = 'xxxxxxxx' [consumer_config.koji_instances.primary.tags.sidetags] @@ -115,6 +117,7 @@ handlers = ["console"] from = 'rawhide-modular-signcandidate' to = 'rawhide-modular' key = 'fedora26' + file_signing_key = 'fedora-file-signing' keyid = 'xxxxxxxx' type = 'modular' diff --git a/robosignatory/tag.py b/robosignatory/tag.py index d277af3..5fd1a2a 100644 --- a/robosignatory/tag.py +++ b/robosignatory/tag.py @@ -54,9 +54,12 @@ class TagSigner(object): for tag in instance_info['tags']: if tag['from'] in instance_obj['tags']: raise Exception('From detected twice: %s' % tag['from']) - instance_obj['tags'][tag['from']] = {'to': tag['to'], - 'key': tag['key'], - 'keyid': tag['keyid']} + instance_obj['tags'][tag['from']] = { + 'to': tag['to'], + 'key': tag['key'], + 'keyid': tag['keyid'], + 'file_signing_key': tag.get('file_signing_key'), + } tag_type = tag.get('type') if tag_type is None: @@ -210,8 +213,13 @@ class TagSigner(object): log.info('Tag not autosigned, skipping') return - log.info('Going to sign %s with %s (%s) and move to %s', - build_nvr, tag_info['key'], tag_info['keyid'], tag_to) + if tag_info['file_signing_key']: + log.info( + 'Going to sign %s with %s (%s) and file signing key %s and move to %s', + build_nvr, tag_info['key'], tag_info['keyid'], tag_info['file_signing_key'], tag_to) + else: + log.info('Going to sign %s with %s (%s) and move to %s', + build_nvr, tag_info['key'], tag_info['keyid'], tag_to) if tag_info['type'] == 'plain': self.signwrite_single_build(build_nvr, build_id, tag_info, instance, koji_instance) @@ -262,9 +270,12 @@ class TagSigner(object): else: to_sign = [key for key, rpm in rpms.items() if not rpm['signed']] log.debug('RPMs needing signing: %s' % to_sign) - cmdline = self.signer.build_sign_cmdline(tag_info['key'], - to_sign, - koji_instance) + cmdline = self.signer.build_sign_cmdline( + tag_info['key'], + to_sign, + koji_instance, + tag_info.get('file_signing_key'), + ) log.debug('Signing command line: %s' % cmdline) ret, stdout, stderr = utils.run_command(cmdline) diff --git a/robosignatory/utils.py b/robosignatory/utils.py index b5b558d..fbcb651 100644 --- a/robosignatory/utils.py +++ b/robosignatory/utils.py @@ -118,10 +118,12 @@ class EchoHelper(BaseSigningHelper): class SigulHelper(BaseSigningHelper): - def __init__(self, user, passphrase_file, config_file=None): + def __init__(self, user, passphrase_file, config_file=None, + file_signing_key_passphrase_file=None): self.user = user self.passphrase_file = passphrase_file self.config_file = config_file + self.file_signing_key_passphrase_file = file_signing_key_passphrase_file def build_cmdline(self, *args): cmdline = ['sigul', '--batch', '--user-name', self.user, @@ -131,7 +133,8 @@ class SigulHelper(BaseSigningHelper): cmdline.extend(args) return cmdline - def build_sign_cmdline(self, key, rpms, koji_instance=None): + def build_sign_cmdline(self, key, rpms, koji_instance=None, + file_signing_key=None): if len(rpms) == 1: sigul_cmd = "sign-rpm" else: @@ -142,6 +145,16 @@ class SigulHelper(BaseSigningHelper): if koji_instance: command.extend(['-k', koji_instance]) + if file_signing_key: + log.debug('Adding file signing key: %s', file_signing_key) + if not self.file_signing_key_passphrase_file: + raise Exception("No passphrase file for file signing key configured") + command.extend([ + '--file-signing-key', file_signing_key, + '--file-signing-key-passphrase-file', + self.file_signing_key_passphrase_file, + ]) + # TODO: See if this always needs to be set or optional # if self.v3: command.append('--v3-signature') diff --git a/tests/test_tag.py b/tests/test_tag.py index e66baef..53af647 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -68,7 +68,30 @@ TEST_CONFIG = { 'key': 'fedora-30', 'keyid': 'OU812I81B4U', 'type': 'modular', - } + }, + { + 'from': 'f31-pending-fsk', + 'to': 'f31', + 'key': 'fedora-31', + 'keyid': 'deadbeef', + 'file_signing_key': 'file-sign-key', + }, + { + 'from': 'f30-signing-pending-fsk', + 'to': 'f30-updates-testing-pending', + 'key': 'fedora-30', + 'keyid': 'OU812I81B4U', + 'file_signing_key': 'file-sign-key', + 'type': 'plain', + }, + { + 'from': 'f30-modular-signing-pending-fsk', + 'to': 'f30-modular-updates-testing-pending', + 'key': 'fedora-30', + 'keyid': 'OU812I81B4U', + 'file_signing_key': 'file-sign-key', + 'type': 'modular', + }, ], }, }, @@ -267,7 +290,7 @@ class TestTagSigner(object): break else: raise RuntimeError("Can't find sidetag configuration for {}".format( - tag_conf['from'])) + sidetag_conf)) if error == 'untrusted-tagger': tagger = 'hamburglar' @@ -390,6 +413,57 @@ class TestTagSigner(object): self.koji_client.tagBuild.assert_not_called() @requires_caplog + @mock.patch('robosignatory.tag.utils', new_callable=MockUtils()) + @mark.parametrize( + 'type_', + (('plain'), + ('modular'),)) + def test_file_signing_key(self, utils, caplog, type_): + """Test with file signing key""" + caplog.set_level(logging.DEBUG) + + body = self.test_msg['body'] + build_nvr = '{name}-{version}-{release}'.format(**body) + body['tag'] = body['tag'] + '-fsk' + from_tag = body['tag'] + build_id = body['build_id'] + tag_conf = self.instance_obj['tags'][from_tag] + to_tag = tag_conf['to'] + tagger = None + build_owner = None + + if type_ == 'modular': + body['tag'] = from_tag = 'f30-modular-signing-pending-fsk' + to_tag = 'f30-modular-updates-testing-pending' + build_owner = TEST_CONFIG['koji_instances']['test']['mbs_user'] + + self.koji_client.listTagged.return_value = [ + {'owner_name': build_owner, 'nvr': build_nvr, 'build_id': build_id} + ] + expected_log_msgs = [ + 'Packages correctly signed, moving to f30-modular-updates-testing-pending', + 'Signing command line: [\'echo\', "build_sign_cmdline: (\'fedora-30\', [\'foo-1-1.fc31.x64_64\', \'foo-libs-1-1.fc31.x64_64\'], \'test\', \'file-sign-key\') {}"]', + 'Going to sign foo-1-1.fc31 with fedora-30 (OU812I81B4U) and file signing key file-sign-key and move to f30-modular-updates-testing-pending', + ] + else: + expected_log_msgs = [ + 'Signing command line: [\'echo\', "build_sign_cmdline: (\'fedora-31\', [\'foo-1-1.fc31.x64_64\', \'foo-libs-1-1.fc31.x64_64\'], \'test\', \'file-sign-key\') {}"]', + 'Going to sign foo-1-1.fc31 with fedora-31 (deadbeef) and file signing key file-sign-key and move to f31', + ] + + msg = Message(**self.test_msg) + self.tag_signer.consume(msg) + + # Equivalent to caplog.messages but compatible with pytest < 3.7 + logged_messages = [record.getMessage() for record in caplog.records] + + for msg in expected_log_msgs: + assert msg in logged_messages + + self.koji_client.tagBuild.assert_called_once_with(to_tag, build_id, + False, from_tag) + + @requires_caplog def test_plain_build_message_with_unconfigured_tag(self, caplog): """Test the behavior with an unconfigured tag""" caplog.set_level(logging.DEBUG) diff --git a/tests/test_utils.py b/tests/test_utils.py index 49dc415..2a81170 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -18,6 +18,17 @@ class TestUtils(unittest.TestCase): passphrase_file='/tmp/wide-open.txt', ) assert type(helper) == robosignatory.utils.SigulHelper + assert helper.file_signing_key_passphrase_file is None + + def test_get_sigul_helper_with_fsk(self): + helper = robosignatory.utils.get_signing_helper( + backend='sigul', + user='ralph', + passphrase_file='/tmp/wide-open.txt', + file_signing_key_passphrase_file='/tmp/wide-closed.txt', + ) + assert type(helper) == robosignatory.utils.SigulHelper + assert helper.file_signing_key_passphrase_file == '/tmp/wide-closed.txt' def test_simple_echo_helper(self): helper = robosignatory.utils.get_signing_helper( @@ -27,3 +38,13 @@ class TestUtils(unittest.TestCase): ) cmdline = helper.build_cmdline('wat') assert cmdline == ["echo", "build_cmdline: ('wat',) {}"] + + def test_simple_echo_helper_with_fsk(self): + helper = robosignatory.utils.get_signing_helper( + backend='echo', + user='ralph', + passphrase_file='/tmp/wide-open.txt', + file_signing_key_passphrase_file='/tmp/wide-closed.txt', + ) + cmdline = helper.build_cmdline('wat') + assert cmdline == ["echo", "build_cmdline: ('wat',) {}"]