From: Julien Cristau Date: Mon, 8 May 2017 08:59:39 +0000 (+0200) Subject: Add codesign bits for secure boot X-Git-Url: https://git.adam-barratt.org.uk/?a=commitdiff_plain;h=0b574f07d6e5276dc368feeca12cabe0fbc64020;p=mirror%2Fdsa-puppet.git Add codesign bits for secure boot --- diff --git a/modules/roles/files/signing/pesign-wrap b/modules/roles/files/signing/pesign-wrap new file mode 100755 index 000000000..a0618961b --- /dev/null +++ b/modules/roles/files/signing/pesign-wrap @@ -0,0 +1,41 @@ +#!/usr/bin/expect -f + +if {[llength $argv] != 4} { + puts stderr "Usage: $argv0 certdir token cert filename" + exit 2 +} + +lassign $argv certdir token cert filename + +set pin $::env(PESIGN_PIN) + +file tempfile output efi.sig + +log_user 0 +spawn pesign --certdir "$certdir" -t "$token" -c "$cert" --sign -d sha256 -i "$filename" --export-signature "$output" --force +expect { + "Enter Password *:" { + send "$pin\n" + exp_continue + } + "Enter passphrase *:" { + send "$pin\n" + exp_continue + } + timeout {close} +} +lassign [wait] wait_pid spawn_id exec_rc wait_code childkilled +# couldn't exec pesign +if {$exec_rc != 0} { + file delete $output + exit 1 +} +# killed by signal (e.g. timeout) +if {$childkilled == "CHILDKILLED"} { + file delete $output + exit 1 +} +# all good? +if {$wait_code == 0} {system cat $output} +file delete $output +exit $wait_code diff --git a/modules/roles/files/signing/secure-boot-code-sign.py b/modules/roles/files/signing/secure-boot-code-sign.py new file mode 100755 index 000000000..04a3344d0 --- /dev/null +++ b/modules/roles/files/signing/secure-boot-code-sign.py @@ -0,0 +1,138 @@ +#!/usr/bin/python3 + +# Copyright (C) 2017 Collabora Ltd +# 2017 Helen Koike +# +# Ported from bash to python3 by Julien Cristau +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. + +import argparse +import configparser +import os +import subprocess +import sys +import tarfile +import tempfile + + +config = {} + + +def sign(extract_dir, signed_dir): + for dirpath, dirnames, filenames in os.walk(extract_dir): + assert dirpath.startswith(extract_dir) + out_dir = signed_dir + dirpath[len(extract_dir):] + os.makedirs(out_dir) + for filename in filenames: + #print(os.path.join(dirpath, filename), file=sys.stderr) + if filename.endswith('.efi') or filename.startswith('vmlinuz-'): + sign_efi(os.path.join(dirpath, filename), os.path.join(out_dir, filename + '.sig')) + elif filename.endswith('.ko'): + sign_kmod(os.path.join(dirpath, filename), os.path.join(out_dir, filename + '.sig')) + else: + print("ignoring %s" % os.path.join(dirpath, filename), file=sys.stderr) + + +def sign_kmod(module_path, signature_path): + assert 'linux_sign_file' in config + assert 'pkcs11_uri' in config + assert 'cert_path' in config + assert 'pin' in config + + env = os.environ.copy() + env['KBUILD_SIGN_PIN'] = config['pin'] + # use check_output instead of check_call as sign-file seems to send random + # stuff to stderr even when it succeeds + subprocess.check_output( + [config['linux_sign_file'], '-d', 'sha256', config['pkcs11_uri'], + config['cert_path'], module_path], + env=env, stderr=subprocess.STDOUT) + os.rename(module_path + '.p7s', signature_path) + + +def sign_efi(efi_path, signature_path): + assert 'sign-efi' in config + assert 'certdir' in config + assert 'token' in config + assert 'certname' in config + assert 'pin' in config + + env = os.environ.copy() + env['PESIGN_PIN'] = config['pin'] + with open(signature_path, 'wb') as out: + subprocess.check_call( + [config['sign-efi'], config['certdir'], config['token'], + config['certname'], efi_path], + env=env, stdout=out) + + +def extract(tar_file, extract_dir): + with tarfile.TarFile.open(fileobj=tar_file, mode="r:xz") as f: + f.extractall(extract_dir) + + +def repack(signed_dir, fileobj): + def cleanup_tarinfo(tarinfo): + tarinfo.path = os.path.relpath('/' + tarinfo.path, signed_dir) + tarinfo.gid = 0 + tarinfo.gname = 'root' + tarinfo.uid = 0 + tarinfo.uname = 'root' + return tarinfo + + with tarfile.TarFile.open(mode='w:xz', fileobj=fileobj) as f: + f.add(signed_dir, filter=cleanup_tarinfo) + + +def main(): + parser = argparse.ArgumentParser( + description='sign files in a tarball') + parser.add_argument('input_tar', metavar='input', type=argparse.FileType('rb'), + help='tarball containing files to be signed') + parser.add_argument('--config', '-c', type=str, + default='/etc/codesign.ini', help='configuration file') + + args = parser.parse_args() + + cp = configparser.RawConfigParser() + cp.read(args.config) + + # path to the sign-file command from Linux + config['linux_sign_file'] = cp.get('commands', 'sign-kmod', + fallback='/usr/lib/linux-kbuild-4.9/scripts/sign-file') + # pkcs11 uri from `p11tool --list-token-urls` + config['pkcs11_uri'] = cp.get('efi', 'pkcs11_uri') + # path to the PEM or DER-format certificate + config['cert_path'] = cp.get('efi', 'cert_path') + + # path to our pesign wrapper script + config['sign-efi'] = cp.get('commands', 'sign-efi', fallback='/usr/local/bin/pesign-wrap') + # path to the nss store + config['certdir'] = cp.get('efi', 'certdir', fallback='/srv/codesign/pki') + # name of the token in the nss store + config['token'] = cp.get('efi','token', fallback='PIV_II (PIV Card Holder pin)') + # name of the cert in the nss store + config['certname'] = cp.get('efi', 'cert', fallback='Certificate for Digital Signature') + + config['pin'] = cp.get('efi', 'pin') + + workdir = tempfile.TemporaryDirectory() + with workdir: + extract_dir = os.path.join(workdir.name, 'in') + signed_dir = os.path.join(workdir.name, 'out') + extract(args.input_tar, extract_dir) + sign(extract_dir, signed_dir) + repack(signed_dir, sys.stdout.buffer) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/modules/roles/manifests/init.pp b/modules/roles/manifests/init.pp index 124c1888e..d486e0477 100644 --- a/modules/roles/manifests/init.pp +++ b/modules/roles/manifests/init.pp @@ -76,6 +76,7 @@ class roles { if has_role('ftp_master') { include roles::ftp_master include roles::dakmaster + include roles::signing } if has_role('ftp.upload.d.o') { include roles::ftp_upload diff --git a/modules/roles/manifests/signing.pp b/modules/roles/manifests/signing.pp new file mode 100644 index 000000000..a959ae3bb --- /dev/null +++ b/modules/roles/manifests/signing.pp @@ -0,0 +1,20 @@ +class roles::signing { + package { 'expect': ensure => installed, } + package { 'pesign': ensure => installed, } + package { 'linux-kbuild-4.9': ensure => installed, } + package { 'libengine-pkcs11-openssl': ensure => installed, } + + file { '/usr/local/bin/pesign-wrap': + owner => 'root', + group => 'root', + mode => '0555', + source => 'puppet:///modules/roles/signing/pesign-wrap', + } + + file { '/usr/local/bin/secure-boot-code-sign': + owner => 'root', + group => 'root', + mode => '0555', + source => 'puppet:///modules/roles/signing/secure-boot-code-sign.py', + } +} diff --git a/modules/sudo/files/sudoers b/modules/sudo/files/sudoers index 2432bcf1f..e6ff9443b 100644 --- a/modules/sudo/files/sudoers +++ b/modules/sudo/files/sudoers @@ -35,6 +35,7 @@ Host_Alias PORTERBOXES = abel, asachi, barriere, eller, falla, fischer, harris, Host_Alias PIUPARTS_SLAVE_HOSTS = piu-slave-bm-a, piu-slave-ubc-01 Host_Alias MQ_HOSTS = rainier, rapoport Host_Alias JENKINSHOSTS = jerea +Host_Alias SIGNINGHOSTS = fasolo # Cmnd alias specification @@ -162,6 +163,8 @@ nagios storace=(debbackup) NOPASSWD: /usr/lib/nagios/plugins/dsa-check-backuppg dak ALL=(dak-unpriv) NOPASSWD: ALL # and ftpmaster can access the role user for their web services %debadmin FTPHOSTS=(dak-web) ALL +# the dak user gets to sign stuff +dak SIGNINGHOSTS=(codesign) /usr/local/bin/secure-boot-code-sign # some groups are in apachectrl on "their" hosts so they can reload apache and update their vhost %apachectrl ALL=(root) /usr/sbin/apache2-vhost-update