def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" private_key = args[0] public_key = args[1] location.set_description("Validate keys") if not "BEGIN PRIVATE KEY" in private_key: raise GenerateError("Private key blob is not a private key.", location=location) if not "BEGIN PUBLIC KEY" in public_key: raise GenerateError("Public key blob is not a public key.", location=location) # enable the daemon (actually set up socket activation) location.set_description("Enableing homed service") self._execute( location.next_line(), system_context, "systemd_enable", "systemd-homed.service", ) # Install keys into /usr: location.set_description("Setup keys") makedirs(system_context, "/usr/share/factory/var/lib/systemd/home", mode=0o700) create_file( system_context, "/usr/share/factory/var/lib/systemd/home/local.private", private_key.encode("utf-8"), mode=0o600, ) create_file( system_context, "/usr/share/factory/var/lib/systemd/home/local.public", public_key.encode("utf-8"), mode=0o600, ) chmod(system_context, 0o600, "/usr/share/factory/var/lib/systemd/home/*") chown(system_context, 0, 0, "/usr/share/factory/var/lib/systemd/home/*") # Set up copying of keys to var: create_file( system_context, "/usr/lib/tmpfiles.d/systemd-homed.conf", textwrap.dedent("""\ C /var/lib/systemd/home - - - - """).encode("utf-8"), mode=0o644, )
def _validate_installation(location: Location, system_context: SystemContext) -> None: hostname = system_context.substitution("HOSTNAME") if hostname is None: raise GenerateError( "Trying to export a system without a hostname.", location=location ) machine_id = system_context.substitution("MACHINE_ID") if machine_id is None: raise GenerateError( "Trying to export a system without " "a machine_id.", location=location )
def _get_initrd_parts(location: Location, path: str) -> typing.List[str]: if not path: raise GenerateError("No initrd-parts directory.", location=location) initrd_parts = [] # type: typing.List[str] for f in glob(os.path.join(path, "*")): if os.path.isfile(f): initrd_parts.append(f) if not initrd_parts: raise GenerateError( 'No initrd-parts found in directory "{}".'.format(path), location=location) initrd_parts.sort() return initrd_parts
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" old_machine_id = system_context.substitution("MACHINE_ID", "") if old_machine_id: raise GenerateError( f'Machine-id was already set to "{old_machine_id}".', location=location, ) machine_id = args[0] system_context.set_substitution("MACHINE_ID", machine_id) machine_id += "\n" self._execute( location.next_line(), system_context, "create", "/etc/machine-id", machine_id, )
def _validate_files(self, location: Location, *files: str) -> None: for f in files: if f and not os.path.isfile(f): raise GenerateError( f'"{self.name}": referenced file "{f}" does not exist.', location=location, )
def _get_initrd_parts(location: Location, path: str) -> typing.List[str]: if not path: raise GenerateError("No initrd-parts directory.", location=location) initrd_parts: typing.List[str] = [] for f in glob(os.path.join(path, "*")): if os.path.isfile(f): initrd_parts.append(f) if not initrd_parts: raise GenerateError( f'No initrd-parts found in directory "{path}".', location=location ) initrd_parts.sort() for ip in initrd_parts: trace(f" Adding into initrd: {ip} ...") return initrd_parts
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" etc = "/etc" localtime = "localtime" etc_localtime = etc + "/" + localtime timezone = args[0] full_timezone = "../usr/share/zoneinfo/" + timezone if not exists(system_context, full_timezone, work_directory=etc): raise GenerateError( f'Timezone "{timezone}" not found when trying to set timezone.', location=location, ) self._execute(location, system_context, "remove", etc_localtime) self._execute( location.next_line(), system_context, "symlink", full_timezone, localtime, work_directory="/etc", )
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" static_hostname = args[0] pretty_hostname = kwargs.get("pretty", static_hostname) if system_context.substitution("HOSTNAME", ""): raise GenerateError("Hostname was already set.", location=location) system_context.set_substitution("HOSTNAME", static_hostname) system_context.set_substitution("PRETTY_HOSTNAME", pretty_hostname) self._execute(location, system_context, "create", "/etc/hostname", static_hostname) self._execute( location.next_line(), system_context, "sed", f'/^PRETTY_HOSTNAME=/ cPRETTY_HOSTNAME="{pretty_hostname}"', "/etc/machine.info", )
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any ) -> None: """Execute command.""" self._usr_only = kwargs.get("usr_only", True) rootfs_file = args[0] rootfs_label = system_context.substitution_expanded("ROOTFS_PARTLABEL", "") if not rootfs_label: raise GenerateError("ROOTFS_PARTLABEL is unset.") target_directory = "usr" if self._usr_only else "." target_args = ["-keep-as-directory"] if self._usr_only else [] run( self._binary(Binaries.MKSQUASHFS), target_directory, rootfs_file, *target_args, "-comp", "gzip", # compression does not matter: We disable compression! "-noappend", "-no-exports", "-noI", "-noD", "-noF", "-noX", "-processors", "1", work_directory=system_context.fs_directory ) size_extend(rootfs_file)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" target = args[0] systemd_directory = "/usr/lib/systemd/system/" target_path = systemd_directory + args[0] if not isfile(system_context, target_path): raise GenerateError( 'Target "{}" does not exist or is no file. ' "Can not use as default target.".format(target)) default = "default.target" default_path = systemd_directory + "default.target" self._execute(location, system_context, "remove", default_path, force=True) self._execute( location.next_line(), system_context, "symlink", target, default, work_directory=systemd_directory, ) system_context.set_substitution("DEFAULT_BOOT_TARGET", target)
def _move_symlink( location: Location, system_context: SystemContext, old_base: str, new_base: str, link: str, ): """Move a symlink.""" root_directory = system_context.fs_directory + "/" link_target = os.readlink(link) # normalize to /usr/lib... if link_target.startswith("/lib/"): link_target = f"/usr{link_target}" (output_link, output_link_target) = _map_host_link(root_directory, old_base, new_base, link, link_target) trace( f"Moving link {link}->{link_target}: {output_link} to {output_link_target}" ) os.makedirs(os.path.dirname(output_link), mode=0o755, exist_ok=True) if not os.path.isdir(os.path.dirname(output_link)): raise GenerateError( f'"{output_link}" is no directory when trying to move "{link}" into /usr.', location=location, ) if os.path.exists(output_link): if not os.path.islink(output_link): raise GenerateError( f'"{output_link}" exists and is not a link when trying to move "{link}" into /usr.', location=location, ) else: old_link_target = os.readlink(output_link) if old_link_target != output_link_target: raise GenerateError( f'"{link}" exists but points to "{old_link_target}" when "{output_link_target}" was expected.', location=location, ) else: os.unlink(link) return # Already correct else: os.symlink(output_link_target, output_link) os.unlink(link)
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" user = args[0] keyfile = args[1] info(f"Adding ssh key to {user}'s authorized_keys file.") data = UserHelper.user_data(user, root_directory=system_context.fs_directory) if data is None: raise GenerateError( f'"{self.name}" could not find user "{user}".', location=location, ) trace(f"{user} mapping: UID {data.uid}, GID {data.gid}, home: {data.home}.") self._check_or_create_directory( location, system_context, data.home, mode=0o750, user=data.uid, group=data.gid, ) ssh_directory = os.path.join(data.home, ".ssh") self._check_or_create_directory( location, system_context, ssh_directory, mode=0o700, user=data.uid, group=data.gid, ) key = read_file(system_context, keyfile, outside=True).decode("utf-8") authorized_file = os.path.join(ssh_directory, "authorized_keys") line = "" options = kwargs.get("options", "") if options: line = options + " " + key + "\n" else: line += key + "\n" self._execute( location.next_line(), system_context, "append", authorized_file, line, force=True, ) chown(system_context, data.uid, data.gid, authorized_file) chmod(system_context, 0o600, authorized_file)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" # FIXME: Implement this! # self._validate_key_directory(location, key_directory) if not isdir(system_context, '/etc/ssh'): raise GenerateError('"{}": No /etc/ssh directory found in system.' .format(self.name), location=location)
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" ## validate package type: if system_context.substitution("CLRM_PACKAGE_TYPE", ""): raise GenerateError( "Trying to run swupd_init on a system that already has a CLRM_PACKAGE_TYPE defined." ) system_context.set_substitution("CLRM_PACKAGE_TYPE", "swupd") run( self._binary(Binaries.SWUPD), "autoupdate", f"--path={system_context.fs_directory}", "--disable", "--no-progress", returncode=28, ) # Setup update-helper so that swupd os-install will actually work: os.makedirs(system_context.file_name("/usr/bin")) with open(system_context.file_name("/usr/bin/update-helper"), "wb") as fd: fd.write( dedent( """\ #!/usr/bin/sh exit 0 """ ).encode("utf-8") ) os.chmod(system_context.file_name("/usr/bin/update-helper"), 0o755) run( self._binary(Binaries.SWUPD), "os-install", f"--path={system_context.fs_directory}", "--skip-optional", "--no-progress", ) location.set_description("Move systemd files into /usr") self._add_hook(location, system_context, "_teardown", "systemd_cleanup") with open(system_context.file_name("/usr/lib/os-release"), "r") as osr: for l in osr: l = l.strip() if l.startswith("BUILD_ID="): build_id = l[9:] verbose(f"Installed {build_id}.") system_context.set_substitution("DISTRO_VERSION_ID", build_id) system_context.set_substitution("DISTRO_VERSION", build_id) self._execute(location.next_line(), system_context, "create_os_release")
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" if system_context.substitution("ROOT_DEVICE") is None: GenerateError("ROOT_DEVICE must be set when creating EFI kernel.", location=location) output = args[0] kernel = kwargs.get("kernel", "") initrd_directory = kwargs.get( "initrd", os.path.join(system_context.boot_directory, "initrd-parts")) initrd_files = _get_initrd_parts(location, initrd_directory) cmdline_input = kwargs.get("commandline", "") osrelease_file = system_context.file_name("/usr/lib/os-release") efistub = system_context.file_name("/usr/lib/systemd/boot/efi/" "linuxx64.efi.stub") debug("{}: Kernel : {}.".format(self.name, kernel)) debug("{}: Initrd : {}.".format(self.name, ", ".join(initrd_files))) debug("{}: cmdline : {}.".format(self.name, cmdline_input)) debug("{}: osrelease: {}.".format(self.name, osrelease_file)) debug("{}: efistub : {}.".format(self.name, efistub)) self._validate_files(kernel, *initrd_files, osrelease_file, efistub) with tempfile.TemporaryDirectory() as tmp: initrd = _create_initrd(tmp, *initrd_files) cmdline = _create_cmdline_file(tmp, cmdline_input) run( self._binary(Binaries.OBJCOPY), "--add-section", ".osrel={}".format(osrelease_file), "--change-section-vma", ".osrel=0x20000", "--add-section", ".cmdline={}".format(cmdline), "--change-section-vma", ".cmdline=0x30000", "--add-section", ".linux={}".format(kernel), "--change-section-vma", ".linux=0x40000", "--add-section", ".initrd={}".format(initrd), "--change-section-vma", ".initrd=0x3000000", efistub, output, ) os.remove(initrd) os.remove(cmdline)
def _move_file(location, old_base, new_base, path): """Move a file.""" path_dir = os.path.dirname(path) path_name = os.path.basename(path) new_dir = _map_base(old_base, new_base, path_dir)[0] if os.path.exists(new_dir) and not os.path.isdir(new_dir): raise GenerateError('"{}" is not a directory when moving "{}".' .format(new_dir, path), location=location) if not os.path.exists(new_dir): os.makedirs(new_dir, 0o755) new_path = os.path.join(new_dir, path_name) if os.path.exists(new_path): raise GenerateError('"{}" already exists when moving "{}".' .format(new_path, path), location=location) shutil.copyfile(path, new_path)
def _move_symlink(location, system_context, old_base, new_base, link): """Move a symlink.""" root_directory = system_context.fs_directory + '/' link_target = os.readlink(link) # normalize to /usr/lib... if link_target.startswith('/lib/'): link_target = '/usr{}'.format(link_target) (output_link, output_link_target) \ = _map_host_link(root_directory, old_base, new_base, link, link_target) trace('Moving link {}->{}: {} to {}' .format(link, link_target, output_link, output_link_target)) os.makedirs(os.path.dirname(output_link), mode=0o755, exist_ok=True) if not os.path.isdir(os.path.dirname(output_link)): raise GenerateError('"{}" is no directory when trying to move ' '"{}" into /usr.'.format(output_link, link), location=location) if os.path.exists(output_link): if not os.path.islink(output_link): raise GenerateError('"{}" exists and is not a link when ' 'trying to move "{}" into /usr.' .format(output_link, link), location=location) else: old_link_target = os.readlink(output_link) if old_link_target != output_link_target: raise GenerateError('"{}" exists but points to "{}" ' 'when "{}" was expected.' .format(link, old_link_target, output_link_target), location=location) else: os.unlink(link) return # Already correct else: os.symlink(output_link_target, output_link) os.unlink(link)
def _check_or_create_directory(self, location: Location, system_context: SystemContext, directory: str, **kwargs: typing.Any) -> None: if not exists(system_context, directory): makedirs(system_context, directory, **kwargs) return if not isdir(system_context, directory): raise GenerateError( '"{}" needs directory "{}", but that exists and is not a directory.' .format(self.name, directory), location=location)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" if system_context.has_substitution('MACHINE_ID'): raise GenerateError('Machine-id was already set.', location=location) machine_id = args[0] system_context.set_substitution('MACHINE_ID', machine_id) machine_id += '\n' self._execute(location.next_line(), system_context, 'create', '/etc/machine-id', machine_id)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" for a in args: location.set_description('Processing application {}.'.format(a)) desktop_file = '/usr/share/applications/{}.desktop'.format(a) if not os.path.exists(system_context.file_name(desktop_file)): raise GenerateError( 'Desktop file "{}" not found.'.format(desktop_file), location=location) self._execute(location.next_line(), system_context, 'sed', '/^Exec=.*$$/ s!^Exec=!Exec=/usr/bin/firejail !', desktop_file)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" user_name = args[0] key_file = args[1] user = UserHelper.user_data(user_name, root_directory=system_context.fs_directory) if user is None: raise GenerateError( '"{}" could not find user "{}".'.format(self.name, user_name), location=location, ) debug('Installing "{}" to user "{}" ({}).'.format( key_file, user_name, user.home)) self._check_or_create_directory( location, system_context, user.home, mode=0o750, user=user.uid, group=user.gid, ) ssh_directory = os.path.join(user.home, ".ssh") self._check_or_create_directory( location, system_context, ssh_directory, mode=0o600, user=user.uid, group=user.gid, ) installed_key_file = os.path.join(ssh_directory, os.path.basename(key_file)) self._execute( location.next_line(), system_context, "copy", key_file, installed_key_file, from_outside=True, ) trace("Copied key.") chown(system_context, user.uid, user.gid, installed_key_file) trace("Ownership adjusted.") chmod(system_context, 0o600, installed_key_file) trace("Mode adjusted.")
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" user = args[0] keyfile = args[1] info('Adding ssh key to {}\'s authorized_keys file.'.format(user)) data = UserHelper.user_data(user, root_directory=system_context.fs_directory) if data is None: raise GenerateError('"{}" could not find user "{}".'.format( self.name, user), location=location) trace('{} mapping: UID {}, GID {}, home: {}.'.format( user, data.uid, data.gid, data.home)) self._check_or_create_directory(location, system_context, data.home, mode=0o750, user=data.uid, group=data.gid) ssh_directory = os.path.join(data.home, '.ssh') self._check_or_create_directory(location, system_context, ssh_directory, mode=0o700, user=data.uid, group=data.gid) key = read_file(system_context, keyfile, outside=True).decode('utf-8') authorized_file = os.path.join(ssh_directory, 'authorized_keys') line = '' options = kwargs.get('options', '') if options: line = options + ' ' + key + '\n' else: line += key + '\n' self._execute(location.next_line(), system_context, 'append', authorized_file, line, force=True) chown(system_context, data.uid, data.gid, authorized_file) chmod(system_context, 0o600, authorized_file)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" uuid = args[0] cmdline = system_context.substitution('KERNEL_CMDLINE', '') if 'rd.luks.name=' in cmdline: raise GenerateError('rd.luks.name already set.', location=location) if cmdline: cmdline += ' ' cmdline += 'rd.luks.name={}={} rd.luks.options=discard'.format( uuid, args[1]) system_context.set_substitution('KERNEL_CMDLINE', cmdline)
def _check_or_create_directory( self, location: Location, system_context: SystemContext, directory: str, **kwargs: typing.Any, ) -> None: if not exists(system_context, directory): self._execute(location.next_line(), system_context, "mkdir", directory, **kwargs) return if not isdir(system_context, directory): raise GenerateError( f'"{self.name}" needs directory "{directory}", but that exists and is not a directory.', location=location, )
def _create_root_tarball(self, location: Location, system_context: SystemContext) -> None: tarball = 'usr/lib/boot/root-fs.tar' os.makedirs(system_context.file_name('/usr/lib/boot')) if exists(system_context, tarball): raise GenerateError( '"{}": Root tarball "{}" already exists.'.format( self.name, tarball), location=location) run(self._binary(Binaries.TAR), '-cf', tarball, '--sort=name', 'etc', 'root', work_directory=system_context.fs_directory)
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" if system_context.has_substitution("MACHINE_ID"): raise GenerateError("Machine-id was already set.", location=location) machine_id = args[0] system_context.set_substitution("MACHINE_ID", machine_id) machine_id += "\n" self._execute( location.next_line(), system_context, "create", "/etc/machine-id", machine_id, )
def __call__(self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any) -> None: """Execute command.""" static_hostname = args[0] pretty_hostname = kwargs.get('pretty', static_hostname) if system_context.has_substitution('HOSTNAME'): raise GenerateError('Hostname was already set.', location=location) system_context.set_substitution('HOSTNAME', static_hostname) system_context.set_substitution('PRETTY_HOSTNAME', pretty_hostname) self._execute(location, system_context, 'create', '/etc/hostname', static_hostname) self._execute( location.next_line(), system_context, 'sed', '/^PRETTY_HOSTNAME=/ cPRETTY_HOSTNAME=\"{}\"'.format( pretty_hostname), '/etc/machine.info')
def _create_root_tarball(self, location: Location, system_context: SystemContext) -> None: tarball = "usr/lib/boot/root-fs.tar" os.makedirs(system_context.file_name("/usr/lib/boot")) if exists(system_context, tarball): raise GenerateError( f'"{self.name}": Root tarball "{tarball}" already exists.', location=location, ) run( self._binary(Binaries.TAR), "-cf", tarball, "--sort=name", "etc", "root", work_directory=system_context.fs_directory, )
def __call__( self, location: Location, system_context: SystemContext, *args: typing.Any, **kwargs: typing.Any, ) -> None: """Execute command.""" uuid = args[0] name = args[1] cmdline = system_context.substitution("KERNEL_CMDLINE", "") if "rd.luks.name=" in cmdline: raise GenerateError("rd.luks.name already set.", location=location) system_context.set_or_append_substitution( "KERNEL_CMDLINE", f"rd.luks.name={uuid}={name} rd.luks.options=discard", )
def _create_rootverity_fsimage(self, location: Location, system_context: SystemContext, *, rootfs: str) -> typing.Tuple[str, str]: vrty_label = system_context.substitution_expanded( "VRTYFS_PARTLABEL", "") if not vrty_label: raise GenerateError("VRTYFS_PARTLABEL is unset.") verity_file = os.path.join(system_context.cache_directory, vrty_label) self._execute( location, system_context, "_create_dmverity_fsimage", verity_file, base_image=rootfs, ) root_hash = system_context.substitution("LAST_DMVERITY_ROOTHASH", "") assert root_hash return (verity_file, root_hash)