diff --git a/Dockerfile.centos-gcc4.8 b/Dockerfile.centos-gcc4.8 index 3ae750b..48a4a26 100644 --- a/Dockerfile.centos-gcc4.8 +++ b/Dockerfile.centos-gcc4.8 @@ -10,6 +10,7 @@ RUN yum -y install \ flex \ make \ cmake \ + openssl \ elfutils-devel \ python-lxml && yum clean all diff --git a/builder-entrypoint.sh b/builder-entrypoint.sh index 8d44364..a336ee9 100755 --- a/builder-entrypoint.sh +++ b/builder-entrypoint.sh @@ -9,6 +9,7 @@ # PROBE_DEVICE_NAME # PROBE_NAME # PROBE_VERSION +# SIGN_FILE_HASH_ALGO # optional env vars # CLANG @@ -56,6 +57,16 @@ build_kmod() { exit 1 fi + if [ -n "${SIGN_FILE_HASH_ALGO}" ]; then + echo "Signing generated kernel module" + ${KERNELDIR}/scripts/sign-file ${SIGN_FILE_HASH_ALGO} \ + /code/sysdig-ro/signing-key/key.priv \ + /code/sysdig-ro/signing-key/key.der \ + driver/${PROBE_NAME}.ko + else + echo "Kernel module will NOT be signed" + fi + cp driver/$PROBE_NAME.ko $OUTPUT/$PROBE_NAME-$PROBE_VERSION-$ARCH-$KERNEL_RELEASE-$HASH.ko cp driver/$PROBE_NAME.ko $OUTPUT/$PROBE_NAME-$PROBE_VERSION-$ARCH-$KERNEL_RELEASE-$HASH_ORIG.ko } diff --git a/probe_builder/__init__.py b/probe_builder/__init__.py index 2504c63..52ae1e5 100644 --- a/probe_builder/__init__.py +++ b/probe_builder/__init__.py @@ -114,10 +114,11 @@ def cli(debug): @click.option('-s', '--source-dir') @click.option('-t', '--download-timeout', type=click.FLOAT) @click.option('-v', '--probe-version') +@click.option('-x', '--sign', is_flag=True) @click.argument('package', nargs=-1) def build(builder_image_prefix, download_concurrency, jobs, kernel_type, filter, probe_name, retries, - source_dir, download_timeout, probe_version, package): + source_dir, download_timeout, probe_version, sign, package): workspace_dir = os.getcwd() builder_source = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -135,7 +136,7 @@ def build(builder_image_prefix, with ThreadPoolExecutor(max_workers=jobs) as executor: kernels_futures = [] for release, target in kernel_dirs: - future = executor.submit(distro_builder.build_kernel, workspace, probe, distro.builder_distro, release, target) + future = executor.submit(distro_builder.build_kernel, workspace, probe, distro.builder_distro, release, target, sign) kernels_futures.append((release, future)) diff --git a/probe_builder/builder/builder_image.py b/probe_builder/builder/builder_image.py index 545760c..86e2b18 100644 --- a/probe_builder/builder/builder_image.py +++ b/probe_builder/builder/builder_image.py @@ -39,7 +39,7 @@ def build(workspace, dockerfile, dockerfile_tag): return obj def run(workspace, probe, kernel_dir, kernel_release, - config_hash, container_name, image_name, args): + config_hash, container_name, image_name, sign_file_hash_algo, args): volumes = [ docker.DockerVolume(workspace.host_dir(probe.sysdig_dir), '/code/sysdig-ro', True), docker.DockerVolume(workspace.host_workspace(), '/build/probe', True), @@ -53,7 +53,8 @@ def run(workspace, probe, kernel_dir, kernel_release, docker.EnvVar('KERNELDIR', kernel_dir.replace(workspace.workspace, '/build/probe/')), docker.EnvVar('KERNEL_RELEASE', kernel_release), docker.EnvVar('HASH', config_hash), - docker.EnvVar('HASH_ORIG', config_hash) + docker.EnvVar('HASH_ORIG', config_hash), + docker.EnvVar('SIGN_FILE_HASH_ALGO', sign_file_hash_algo), ] return docker.run(image_name, volumes, args, env, name=container_name) diff --git a/probe_builder/builder/distro/base_builder.py b/probe_builder/builder/distro/base_builder.py index a55504f..689ac48 100644 --- a/probe_builder/builder/distro/base_builder.py +++ b/probe_builder/builder/distro/base_builder.py @@ -1,3 +1,4 @@ +import configparser import errno import logging import os @@ -61,17 +62,32 @@ def md5sum(path): digest.update(fp.read().encode('utf-8')) return digest.hexdigest() + @staticmethod + def config_module_sig_hash(path): + # Look up the default hashing algorithm for module signing from within the config file + # Notes: + # 1. If not present, use empty string (no signing) + # 2. Remove the quotes + with open(path, 'r') as f: + config_string = '[DEFAULT]\n' + f.read() + config = configparser.ConfigParser() + config.read_string(config_string) + return config['DEFAULT'].get('CONFIG_MODULE_SIG_HASH','').replace('"','') + def unpack_kernels(self, workspace, distro, kernels): raise NotImplementedError def hash_config(self, release, target): raise NotImplementedError + def sign_file_hash_algo(self, release, target): + raise NotImplementedError + def get_kernel_dir(self, workspace, release, target): raise NotImplementedError @classmethod - def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir, probe, release, workspace, bpf, + def build_kernel_impl(cls, config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, bpf, skip_reason): if bpf: label = 'eBPF' @@ -91,7 +107,7 @@ def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir, #docker.rm(container_name) try: ts0 = time.time() - stdout = builder_image.run(workspace, probe, kernel_dir, release, config_hash, container_name, image_name, args) + stdout = builder_image.run(workspace, probe, kernel_dir, release, config_hash, container_name, image_name, sign_file_hash_algo, args) except subprocess.CalledProcessError as e: took = time.time() - ts0 logger.error("Build failed for {} probe {}-{} (took {:.3f}s)".format(label, release, config_hash, took)) @@ -107,8 +123,9 @@ def build_kernel_impl(cls, config_hash, container_name, image_name, kernel_dir, logger.warn(make_string(line)) return cls.ProbeBuildResult(cls.ProbeBuildResult.BUILD_FAILED, took, stdout) - def build_kernel(self, workspace, probe, builder_distro, release, target): + def build_kernel(self, workspace, probe, builder_distro, release, target, do_sign=False): config_hash = self.hash_config(release, target) + sign_file_hash_algo = self.sign_file_hash_algo(release, target) if do_sign else "" output_dir = workspace.subdir('output') kmod_skip_reason = builder_image.skip_build(probe, output_dir, release, config_hash, False) @@ -138,9 +155,9 @@ def build_kernel(self, workspace, probe, builder_distro, release, target): container_name = '' return self.KernelBuildResult( - self.build_kernel_impl(config_hash, container_name, image_name, kernel_dir, probe, release, workspace, False, + self.build_kernel_impl(config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, False, kmod_skip_reason), - self.build_kernel_impl(config_hash, container_name, image_name, kernel_dir, probe, release, workspace, True, + self.build_kernel_impl(config_hash, sign_file_hash_algo, container_name, image_name, kernel_dir, probe, release, workspace, True, ebpf_skip_reason), ) diff --git a/probe_builder/builder/distro/centos.py b/probe_builder/builder/distro/centos.py index 98c94f9..9671f0f 100644 --- a/probe_builder/builder/distro/centos.py +++ b/probe_builder/builder/distro/centos.py @@ -33,6 +33,17 @@ def hash_config(self, release, target): except IOError: return self.md5sum(os.path.join(target, 'lib/modules/{}/config'.format(release))) + def sign_file_hash_algo(self, release, target): + boot = os.path.join(target, 'boot/config-{}'.format(release)) + lib_modules = os.path.join(target, 'lib/modules/{}/config'.format(release)) + + if os.path.isfile(boot): + return self.config_module_sig_hash(boot) + elif os.path.isfile(lib_modules): + return self.config_module_sig_hash(lib_modules) + else: + raise Exception("No file found!") + def get_kernel_dir(self, workspace, release, target): return workspace.subdir(target, 'usr/src/kernels', release) diff --git a/probe_builder/builder/distro/debian.py b/probe_builder/builder/distro/debian.py index 3977843..7bf49e8 100644 --- a/probe_builder/builder/distro/debian.py +++ b/probe_builder/builder/distro/debian.py @@ -88,6 +88,9 @@ def unpack_kernels(self, workspace, distro, kernels): def hash_config(self, release, target): return self.md5sum(os.path.join(target, 'boot/config-{}'.format(release))) + def sign_file_hash_algo(self, release, target): + return self.config_module_sig_hash(os.path.join(target, 'boot/config-{}'.format(release))) + def get_kernel_dir(self, workspace, release, target): return workspace.subdir(target, 'usr/src/linux-headers-{}'.format(release)) diff --git a/probe_builder/builder/distro/flatcar.py b/probe_builder/builder/distro/flatcar.py index a07f722..3e2e2f6 100644 --- a/probe_builder/builder/distro/flatcar.py +++ b/probe_builder/builder/distro/flatcar.py @@ -32,6 +32,9 @@ def unpack_kernels(self, workspace, distro, kernels): def hash_config(self, release, target): return self.md5sum(os.path.join(target, 'config'.format(release))) + def sign_file_hash_algo(self, release, target): + return self.config_module_sig_hash(os.path.join(target, 'config'.format(release))) + def get_kernel_dir(self, workspace, release, target): versions = glob.glob(os.path.join(target, 'modules/*/build')) if len(versions) != 1: diff --git a/probe_builder/builder/distro/ubuntu.py b/probe_builder/builder/distro/ubuntu.py index fc17ba9..784df6d 100644 --- a/probe_builder/builder/distro/ubuntu.py +++ b/probe_builder/builder/distro/ubuntu.py @@ -68,6 +68,9 @@ def unpack_kernels(self, workspace, distro, kernels): def hash_config(self, release, target): return self.md5sum(os.path.join(target, 'boot/config-{}'.format(release))) + def sign_file_hash_algo(self, release, target): + return self.config_module_sig_hash(os.path.join(target, 'boot/config-{}'.format(release))) + def get_kernel_dir(self, workspace, release, target): return workspace.subdir(target, 'usr/src/linux-headers-{}'.format(release))