Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'build_files/buildbot/codesign/base_code_signer.py')
-rw-r--r--build_files/buildbot/codesign/base_code_signer.py501
1 files changed, 0 insertions, 501 deletions
diff --git a/build_files/buildbot/codesign/base_code_signer.py b/build_files/buildbot/codesign/base_code_signer.py
deleted file mode 100644
index f045e9c8242..00000000000
--- a/build_files/buildbot/codesign/base_code_signer.py
+++ /dev/null
@@ -1,501 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# 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 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.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-# Signing process overview.
-#
-# From buildbot worker side:
-# - Files which needs to be signed are collected from either a directory to
-# sign all signable files in there, or by filename of a single file to sign.
-# - Those files gets packed into an archive and stored in a location location
-# which is watched by the signing server.
-# - A marker READY file is created which indicates the archive is ready for
-# access.
-# - Wait for the server to provide an archive with signed files.
-# This is done by watching for the READY file which corresponds to an archive
-# coming from the signing server.
-# - Unpack the signed signed files from the archives and replace original ones.
-#
-# From code sign server:
-# - Watch special location for a READY file which indicates the there is an
-# archive with files which are to be signed.
-# - Unpack the archive to a temporary location.
-# - Run codesign tool and make sure all the files are signed.
-# - Pack the signed files and store them in a location which is watched by
-# the buildbot worker.
-# - Create a READY file which indicates that the archive with signed files is
-# ready.
-
-import abc
-import logging
-import shutil
-import subprocess
-import time
-import tarfile
-import uuid
-
-from pathlib import Path
-from tempfile import TemporaryDirectory
-from typing import Iterable, List
-
-import codesign.util as util
-
-from codesign.absolute_and_relative_filename import AbsoluteAndRelativeFileName
-from codesign.archive_with_indicator import ArchiveWithIndicator
-from codesign.exception import CodeSignException
-
-
-logger = logging.getLogger(__name__)
-logger_builder = logger.getChild('builder')
-logger_server = logger.getChild('server')
-
-
-def pack_files(files: Iterable[AbsoluteAndRelativeFileName],
- archive_filepath: Path) -> None:
- """
- Create tar archive from given files for the signing pipeline.
- Is used by buildbot worker to create an archive of files which are to be
- signed, and by signing server to send signed files back to the worker.
- """
- with tarfile.TarFile.open(archive_filepath, 'w') as tar_file_handle:
- for file_info in files:
- tar_file_handle.add(file_info.absolute_filepath,
- arcname=file_info.relative_filepath)
-
-
-def extract_files(archive_filepath: Path,
- extraction_dir: Path) -> None:
- """
- Extract all files form the given archive into the given direcotry.
- """
-
- # TODO(sergey): Verify files in the archive have relative path.
-
- with tarfile.TarFile.open(archive_filepath, mode='r') as tar_file_handle:
- tar_file_handle.extractall(path=extraction_dir)
-
-
-class BaseCodeSigner(metaclass=abc.ABCMeta):
- """
- Base class for a platform-specific signer of binaries.
-
- Contains all the logic shared across platform-specific implementations, such
- as synchronization and notification logic.
-
- Platform specific bits (such as actual command for signing the binary) are
- to be implemented as a subclass.
-
- Provides utilities code signing as a whole, including functionality needed
- by a signing server and a buildbot worker.
-
- The signer and builder may run on separate machines, the only requirement is
- that they have access to a directory which is shared between them. For the
- security concerns this is to be done as a separate machine (or as a Shared
- Folder configuration in VirtualBox configuration). This directory might be
- mounted under different base paths, but its underlying storage is to be
- the same.
-
- The code signer is short-lived on a buildbot worker side, and is living
- forever on a code signing server side.
- """
-
- # TODO(sergey): Find a neat way to have config annotated.
- # config: Config
-
- # Storage directory where builder puts files which are requested to be
- # signed.
- # Consider this an input of the code signing server.
- unsigned_storage_dir: Path
-
- # Storage where signed files are stored.
- # Consider this an output of the code signer server.
- signed_storage_dir: Path
-
- # Platform the code is currently executing on.
- platform: util.Platform
-
- def __init__(self, config):
- self.config = config
-
- absolute_shared_storage_dir = config.SHARED_STORAGE_DIR.resolve()
-
- # Unsigned (signing server input) configuration.
- self.unsigned_storage_dir = absolute_shared_storage_dir / 'unsigned'
-
- # Signed (signing server output) configuration.
- self.signed_storage_dir = absolute_shared_storage_dir / 'signed'
-
- self.platform = util.get_current_platform()
-
- def cleanup_environment_for_builder(self) -> None:
- # TODO(sergey): Revisit need of cleaning up the existing files.
- # In practice it wasn't so helpful, and with multiple clients
- # talking to the same server it becomes even more tricky.
- pass
-
- def cleanup_environment_for_signing_server(self) -> None:
- # TODO(sergey): Revisit need of cleaning up the existing files.
- # In practice it wasn't so helpful, and with multiple clients
- # talking to the same server it becomes even more tricky.
- pass
-
- def generate_request_id(self) -> str:
- """
- Generate an unique identifier for code signing request.
- """
- return str(uuid.uuid4())
-
- def archive_info_for_request_id(
- self, path: Path, request_id: str) -> ArchiveWithIndicator:
- return ArchiveWithIndicator(
- path, f'{request_id}.tar', f'{request_id}.ready')
-
- def signed_archive_info_for_request_id(
- self, request_id: str) -> ArchiveWithIndicator:
- return self.archive_info_for_request_id(
- self.signed_storage_dir, request_id)
-
- def unsigned_archive_info_for_request_id(
- self, request_id: str) -> ArchiveWithIndicator:
- return self.archive_info_for_request_id(
- self.unsigned_storage_dir, request_id)
-
- ############################################################################
- # Buildbot worker side helpers.
-
- @abc.abstractmethod
- def check_file_is_to_be_signed(
- self, file: AbsoluteAndRelativeFileName) -> bool:
- """
- Check whether file is to be signed.
-
- Is used by both single file signing pipeline and recursive directory
- signing pipeline.
-
- This is where code signer is to check whether file is to be signed or
- not. This check might be based on a simple extension test or on actual
- test whether file have a digital signature already or not.
- """
-
- def collect_files_to_sign(self, path: Path) \
- -> List[AbsoluteAndRelativeFileName]:
- """
- Get all files which need to be signed from the given path.
-
- NOTE: The path might either be a file or directory.
-
- This function is run from the buildbot worker side.
- """
-
- # If there is a single file provided trust the buildbot worker that it
- # is eligible for signing.
- if path.is_file():
- file = AbsoluteAndRelativeFileName.from_path(path)
- if not self.check_file_is_to_be_signed(file):
- return []
- return [file]
-
- all_files = AbsoluteAndRelativeFileName.recursively_from_directory(
- path)
- files_to_be_signed = [file for file in all_files
- if self.check_file_is_to_be_signed(file)]
- return files_to_be_signed
-
- def wait_for_signed_archive_or_die(self, request_id) -> None:
- """
- Wait until archive with signed files is available.
-
- Will only return if the archive with signed files is available. If there
- was an error during code sign procedure the SystemExit exception is
- raised, with the message set to the error reported by the codesign
- server.
-
- Will only wait for the configured time. If that time exceeds and there
- is still no responce from the signing server the application will exit
- with a non-zero exit code.
-
- """
-
- signed_archive_info = self.signed_archive_info_for_request_id(
- request_id)
- unsigned_archive_info = self.unsigned_archive_info_for_request_id(
- request_id)
-
- timeout_in_seconds = self.config.TIMEOUT_IN_SECONDS
- time_start = time.monotonic()
- while not signed_archive_info.is_ready():
- time.sleep(1)
- time_slept_in_seconds = time.monotonic() - time_start
- if time_slept_in_seconds > timeout_in_seconds:
- signed_archive_info.clean()
- unsigned_archive_info.clean()
- raise SystemExit("Signing server didn't finish signing in "
- f'{timeout_in_seconds} seconds, dying :(')
-
- archive_state = signed_archive_info.get_state()
- if archive_state.has_error():
- signed_archive_info.clean()
- unsigned_archive_info.clean()
- raise SystemExit(
- f'Error happenned during codesign procedure: {archive_state.error_message}')
-
- def copy_signed_files_to_directory(
- self, signed_dir: Path, destination_dir: Path) -> None:
- """
- Copy all files from signed_dir to destination_dir.
-
- This function will overwrite any existing file. Permissions are copied
- from the source files, but other metadata, such as timestamps, are not.
- """
- for signed_filepath in signed_dir.glob('**/*'):
- if not signed_filepath.is_file():
- continue
-
- relative_filepath = signed_filepath.relative_to(signed_dir)
- destination_filepath = destination_dir / relative_filepath
- destination_filepath.parent.mkdir(parents=True, exist_ok=True)
-
- shutil.copy(signed_filepath, destination_filepath)
-
- def run_buildbot_path_sign_pipeline(self, path: Path) -> None:
- """
- Run all steps needed to make given path signed.
-
- Path points to an unsigned file or a directory which contains unsigned
- files.
-
- If the path points to a single file then this file will be signed.
- This is used to sign a final bundle such as .msi on Windows or .dmg on
- macOS.
-
- NOTE: The code signed implementation might actually reject signing the
- file, in which case the file will be left unsigned. This isn't anything
- to be considered a failure situation, just might happen when buildbot
- worker can not detect whether signing is really required in a specific
- case or not.
-
- If the path points to a directory then code signer will sign all
- signable files from it (finding them recursively).
- """
-
- self.cleanup_environment_for_builder()
-
- # Make sure storage directory exists.
- self.unsigned_storage_dir.mkdir(parents=True, exist_ok=True)
-
- # Collect all files which needs to be signed and pack them into a single
- # archive which will be sent to the signing server.
- logger_builder.info('Collecting files which are to be signed...')
- files = self.collect_files_to_sign(path)
- if not files:
- logger_builder.info('No files to be signed, ignoring.')
- return
- logger_builder.info('Found %d files to sign.', len(files))
-
- request_id = self.generate_request_id()
- signed_archive_info = self.signed_archive_info_for_request_id(
- request_id)
- unsigned_archive_info = self.unsigned_archive_info_for_request_id(
- request_id)
-
- pack_files(files=files,
- archive_filepath=unsigned_archive_info.archive_filepath)
- unsigned_archive_info.tag_ready()
-
- # Wait for the signing server to finish signing.
- logger_builder.info('Waiting signing server to sign the files...')
- self.wait_for_signed_archive_or_die(request_id)
-
- # Extract signed files from archive and move files to final location.
- with TemporaryDirectory(prefix='blender-buildbot-') as temp_dir_str:
- unpacked_signed_files_dir = Path(temp_dir_str)
-
- logger_builder.info('Extracting signed files from archive...')
- extract_files(
- archive_filepath=signed_archive_info.archive_filepath,
- extraction_dir=unpacked_signed_files_dir)
-
- destination_dir = path
- if destination_dir.is_file():
- destination_dir = destination_dir.parent
- self.copy_signed_files_to_directory(
- unpacked_signed_files_dir, destination_dir)
-
- logger_builder.info('Removing archive with signed files...')
- signed_archive_info.clean()
-
- ############################################################################
- # Signing server side helpers.
-
- def wait_for_sign_request(self) -> str:
- """
- Wait for the buildbot to request signing of an archive.
-
- Returns an identifier of signing request.
- """
-
- # TOOD(sergey): Support graceful shutdown on Ctrl-C.
-
- logger_server.info(
- f'Waiting for a request directory {self.unsigned_storage_dir} to appear.')
- while not self.unsigned_storage_dir.exists():
- time.sleep(1)
-
- logger_server.info(
- 'Waiting for a READY indicator of any signing request.')
- request_id = None
- while request_id is None:
- for file in self.unsigned_storage_dir.iterdir():
- if file.suffix != '.ready':
- continue
- request_id = file.stem
- logger_server.info(f'Found READY for request ID {request_id}.')
- if request_id is None:
- time.sleep(1)
-
- unsigned_archive_info = self.unsigned_archive_info_for_request_id(
- request_id)
- while not unsigned_archive_info.is_ready():
- time.sleep(1)
-
- return request_id
-
- @abc.abstractmethod
- def sign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
- """
- Sign all files in the given directory.
-
- NOTE: Signing should happen in-place.
- """
-
- def run_signing_pipeline(self, request_id: str):
- """
- Run the full signing pipeline starting from the point when buildbot
- worker have requested signing.
- """
-
- # Make sure storage directory exists.
- self.signed_storage_dir.mkdir(parents=True, exist_ok=True)
-
- with TemporaryDirectory(prefix='blender-codesign-') as temp_dir_str:
- temp_dir = Path(temp_dir_str)
-
- signed_archive_info = self.signed_archive_info_for_request_id(
- request_id)
- unsigned_archive_info = self.unsigned_archive_info_for_request_id(
- request_id)
-
- logger_server.info('Extracting unsigned files from archive...')
- extract_files(
- archive_filepath=unsigned_archive_info.archive_filepath,
- extraction_dir=temp_dir)
-
- logger_server.info('Collecting all files which needs signing...')
- files = AbsoluteAndRelativeFileName.recursively_from_directory(
- temp_dir)
-
- logger_server.info('Signing all requested files...')
- try:
- self.sign_all_files(files)
- except CodeSignException as error:
- signed_archive_info.tag_ready(error_message=error.message)
- unsigned_archive_info.clean()
- logger_server.info('Signing is complete with errors.')
- return
-
- logger_server.info('Packing signed files...')
- pack_files(files=files,
- archive_filepath=signed_archive_info.archive_filepath)
- signed_archive_info.tag_ready()
-
- logger_server.info('Removing signing request...')
- unsigned_archive_info.clean()
-
- logger_server.info('Signing is complete.')
-
- def run_signing_server(self):
- logger_server.info('Starting new code signing server...')
- self.cleanup_environment_for_signing_server()
- logger_server.info('Code signing server is ready')
- while True:
- logger_server.info('Waiting for the signing request in %s...',
- self.unsigned_storage_dir)
- request_id = self.wait_for_sign_request()
-
- logger_server.info(
- f'Beging signign procedure for request ID {request_id}.')
- self.run_signing_pipeline(request_id)
-
- ############################################################################
- # Command executing.
- #
- # Abstracted to a degree that allows to run commands from a foreign
- # platform.
- # The goal with this is to allow performing dry-run tests of code signer
- # server from other platforms (for example, to test that macOS code signer
- # does what it is supposed to after doing a refactor on Linux).
-
- # TODO(sergey): What is the type annotation for the command?
- def run_command_or_mock(self, command, platform: util.Platform) -> None:
- """
- Run given command if current platform matches given one
-
- If the platform is different then it will only be printed allowing
- to verify logic of the code signing process.
- """
-
- if platform != self.platform:
- logger_server.info(
- f'Will run command for {platform}: {command}')
- return
-
- logger_server.info(f'Running command: {command}')
- subprocess.run(command)
-
- # TODO(sergey): What is the type annotation for the command?
- def check_output_or_mock(self, command,
- platform: util.Platform,
- allow_nonzero_exit_code=False) -> str:
- """
- Run given command if current platform matches given one
-
- If the platform is different then it will only be printed allowing
- to verify logic of the code signing process.
-
- If allow_nonzero_exit_code is truth then the output will be returned
- even if application quit with non-zero exit code.
- Otherwise an subprocess.CalledProcessError exception will be raised
- in such case.
- """
-
- if platform != self.platform:
- logger_server.info(
- f'Will run command for {platform}: {command}')
- return
-
- if allow_nonzero_exit_code:
- process = subprocess.Popen(command,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- output = process.communicate()[0]
- return output.decode()
-
- logger_server.info(f'Running command: {command}')
- return subprocess.check_output(
- command, stderr=subprocess.STDOUT).decode()