From 7b626d61d9ad15aedd7b6994817e9db49b9d68e9 Mon Sep 17 00:00:00 2001 From: Uwe Schwaeke Date: Mon, 9 Feb 2026 11:30:55 +0100 Subject: [PATCH 1/3] cbscore/builder: ignore cosign install if already installed * what: if the return code of the rpm process is 2, check if the failure reason is that the package is already installed. * why: when reusing a container, the package might already be present. this occurs when a build runner job must be debugged. Signed-off-by: Uwe Schwaeke --- cbscore/src/cbscore/builder/prepare.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cbscore/src/cbscore/builder/prepare.py b/cbscore/src/cbscore/builder/prepare.py index 7db78250..7c6c1119 100644 --- a/cbscore/src/cbscore/builder/prepare.py +++ b/cbscore/src/cbscore/builder/prepare.py @@ -105,16 +105,19 @@ async def _cb(s: str) -> None: raise BuilderError(msg="unable to install dependencies") # install cosign rpm - rc, _, stderr = await async_run_cmd( + rc, stdout, stderr = await async_run_cmd( [ "rpm", "-Uvh", "https://github.com/sigstore/cosign/releases/download/v2.4.3/" + "cosign-2.4.3-1.x86_64.rpm", ], - outcb=_cb, ) - if rc != 0: + logger.debug(stdout) + if rc == 2 and re.match(".*already installed.*", stderr): + msg = f'skip install cosign. allready installed' + logger.debug(msg) + elif rc != 0: msg = f"error installing cosign package: {stderr}" logger.error(msg) raise BuilderError(msg) From 9025084ea187a02106690ecb38da4ef9f24b935d Mon Sep 17 00:00:00 2001 From: Uwe Schwaeke Date: Mon, 9 Feb 2026 11:31:46 +0100 Subject: [PATCH 2/3] cbscore: let skopeo handle local registries * what: add option --tls-verify to subcommands build and runner build. pass the tls-verify flag to skopeo when querying the registry. check if the return value from skopeo inspect equals "not found" (exit code 2). * why: if the image is pushed to a local container registry with a self-signed certificate, skopeo must not verify the certificate to avoid errors. current versions of skopeo (1.20.0) return exit code 2 if an image is not found. Signed-off-by: Uwe Schwaeke --- cbscore/src/cbscore/builder/builder.py | 5 ++++- cbscore/src/cbscore/cmds/builds.py | 15 +++++++++++++++ cbscore/src/cbscore/images/skopeo.py | 14 +++++++++----- cbscore/src/cbscore/runner.py | 4 +++- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cbscore/src/cbscore/builder/builder.py b/cbscore/src/cbscore/builder/builder.py index 5a838c25..984f2b57 100644 --- a/cbscore/src/cbscore/builder/builder.py +++ b/cbscore/src/cbscore/builder/builder.py @@ -64,6 +64,7 @@ class Builder: ccache_path: Path | None skip_build: bool force: bool + tls_verify: bool def __init__( self, @@ -72,6 +73,7 @@ def __init__( *, skip_build: bool = False, force: bool = False, + tls_verify: bool = True ) -> None: self.desc = desc self.config = config @@ -82,6 +84,7 @@ def __init__( self.ccache_path = config.paths.ccache self.skip_build = skip_build self.force = force + self.tls_verify = tls_verify try: vault_config = self.config.get_vault_config() @@ -109,7 +112,7 @@ async def run(self) -> None: raise BuilderError(msg=msg) from e container_img_uri = get_container_canonical_uri(self.desc) - if skopeo_image_exists(container_img_uri, self.secrets): + if skopeo_image_exists(container_img_uri, self.secrets, tls_verify=self.tls_verify): logger.info(f"image '{container_img_uri}' already exists -- do not build!") return else: diff --git a/cbscore/src/cbscore/cmds/builds.py b/cbscore/src/cbscore/cmds/builds.py index 84a56a4f..44ae6fb7 100644 --- a/cbscore/src/cbscore/cmds/builds.py +++ b/cbscore/src/cbscore/cmds/builds.py @@ -124,6 +124,11 @@ is_flag=True, default=False, ) +@click.option( + "--tls-verify", + help="Require HTTPS and verify certificates when talking to the container registry or daemon.", + default=True, +) @pass_ctx def cmd_build( ctx: Ctx, @@ -136,6 +141,7 @@ def cmd_build( log_file_path: Path | None, skip_build: bool, force: bool, + tls_verify: bool, ) -> None: assert ctx.config_path @@ -192,6 +198,7 @@ def cmd_build( log_file_path=log_file_path, skip_build=skip_build, force=force, + tls_verify=tls_verify, ) ) @@ -245,12 +252,18 @@ def cmd_runner_grp() -> None: is_flag=True, default=False, ) +@click.option( + "--tls-verify", + help="Require HTTPS and verify certificates when talking to the container registry or daemon.", + default=True, +) @with_config def cmd_runner_build( config: Config, desc_path: Path, skip_build: bool, force: bool, + tls_verify: bool, ) -> None: upload_to_str = ( config.storage.s3.url @@ -285,6 +298,7 @@ def cmd_runner_build( registry: {registry_str} skip build: {skip_build} force: {force} + tls-verify: {tls_verify} """) if not desc_path.exists(): @@ -303,6 +317,7 @@ def cmd_runner_build( config, skip_build=skip_build, force=force, + tls_verify=tls_verify, ) except BuilderError as e: logger.error(f"unable to initialize builder: {e}") diff --git a/cbscore/src/cbscore/images/skopeo.py b/cbscore/src/cbscore/images/skopeo.py index 511152df..b4514987 100644 --- a/cbscore/src/cbscore/images/skopeo.py +++ b/cbscore/src/cbscore/images/skopeo.py @@ -112,7 +112,7 @@ def skopeo_copy( logger.info(f"signed image '{dst}': {out}") -def skopeo_inspect(img: str, secrets: SecretsMgr) -> str: +def skopeo_inspect(img: str, secrets: SecretsMgr, *, tls_verify: bool = True) -> str: logger.debug(f"inspect image '{img}'") try: @@ -132,6 +132,7 @@ def skopeo_inspect(img: str, secrets: SecretsMgr) -> str: retcode, raw_out, err = skopeo( [ "inspect", + f"--tls-verify={tls_verify}", "--creds", Password(f"{user}:{passwd}"), f"docker://{img}", @@ -143,19 +144,22 @@ def skopeo_inspect(img: str, secrets: SecretsMgr) -> str: if retcode != 0: msg = f"error inspecting image '{img}': {err}" - logger.error(msg) - if re.match(r".*not\s+found.*", err): + if retcode == 2 or re.match(r".*not\s+found.*", err): + logger.debug(msg) raise ImageNotFoundError(img) from None + logger.error(msg) raise SkopeoError(msg) from None return raw_out -def skopeo_image_exists(img: str, secrets: SecretsMgr) -> bool: +def skopeo_image_exists( + img: str, secrets: SecretsMgr, *, tls_verify: bool = True +) -> bool: logger.debug(f"check if image '{img}' exists") try: - _ = skopeo_inspect(img, secrets) + _ = skopeo_inspect(img, secrets, tls_verify=tls_verify) except ImageNotFoundError: logger.debug(f"image '{img}' does not exist") return False diff --git a/cbscore/src/cbscore/runner.py b/cbscore/src/cbscore/runner.py index 7e3318a1..c3029f85 100644 --- a/cbscore/src/cbscore/runner.py +++ b/cbscore/src/cbscore/runner.py @@ -118,6 +118,7 @@ async def runner( log_out_cb: AsyncRunCmdOutCallback | None = None, skip_build: bool = False, force: bool = False, + tls_verify: bool = True, ) -> None: our_actual_loc = Path(__file__).parent @@ -174,6 +175,7 @@ async def runner( log to file: {log_file_path if log_file_path else "not logging to file"} skip build: {skip_build} force: {force} + tls-verify: {tls_verify} """) if not entrypoint_path.exists() or not entrypoint_path.is_file(): @@ -250,7 +252,7 @@ async def runner( logger.error(msg) raise RunnerError(msg) from e - podman_args = ["--desc", desc_mount_loc] + podman_args = ["--desc", desc_mount_loc, f"--tls-verify={tls_verify}"] podman_volumes = { desc_file_path.resolve().as_posix(): desc_mount_loc, cbscore_path.resolve().as_posix(): "/runner/cbscore", From c88a926358a72c911d7ddeb882076ec667fbdce1 Mon Sep 17 00:00:00 2001 From: Uwe Schwaeke Date: Tue, 10 Feb 2026 17:34:16 +0100 Subject: [PATCH 3/3] cbscore: sign image only if signing keys are available * what: check if the vault and transit key is available in the secrets file. retrieve user credentials for the registry and transit. if all credentials are available, sign the image with cosign; otherwise, skip signing. * why: run cbsbuild locally for testing, the image signing step must be skipped if keys are missing. see https://github.com/clyso/cbs/issues/24 Signed-off-by: Uwe Schwaeke --- cbscore/src/cbscore/images/signing.py | 49 +++++++++++++++++++-------- cbscore/src/cbscore/images/skopeo.py | 23 +++++++------ 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/cbscore/src/cbscore/images/signing.py b/cbscore/src/cbscore/images/signing.py index 8e017675..a2373246 100644 --- a/cbscore/src/cbscore/images/signing.py +++ b/cbscore/src/cbscore/images/signing.py @@ -36,34 +36,53 @@ def __str__(self) -> str: return f"Signing Error: {self.msg}" -def sign( - registry: str, img: str, secrets: SecretsMgr, transit: str -) -> tuple[int, str, str]: +def _get_signing_params( + registry: str, secrets: SecretsMgr, transit: str +) -> tuple[str, str, str, str]: + """ + Check preconditions and return (username, password, transit_mount, transit_key). + + Raises SigningError if any prerequisite is missing. + """ if not secrets.has_vault(): - msg = "no vault configured, can't sign image" - logger.error(msg) - raise SigningError(msg) + raise SigningError("no vault configured, can't sign image") assert secrets.vault is not None if not secrets.has_transit_key(transit): - msg = f"vault transit key '{transit}' not found, can't sign image" - logger.error(msg) - raise SigningError(msg) + raise SigningError(f"vault transit key '{transit}' not found, can't sign image") try: _, username, password = secrets.registry_creds(registry) except SecretsMgrError as e: - msg = f"unable to obtain registry credentials for '{registry}': {e}" - logger.error(msg) - raise SigningError(msg) from e + raise SigningError( + f"unable to obtain registry credentials for '{registry}': {e}" + ) from e try: transit_mount, transit_key = secrets.transit(transit) except SecretsMgrError as e: - msg = f"unable to obtain transit key '{transit}': {e}" - logger.error(msg) - raise SigningError(msg) from e + raise SigningError(f"unable to obtain transit key '{transit}': {e}") from e + + return username, password, transit_mount, transit_key + + +def can_sign(registry: str, secrets: SecretsMgr, transit: str) -> bool: + try: + _get_signing_params(registry, secrets, transit) + except SigningError as e: + logger.debug(e.msg) + return False + else: + return True + + +def sign( + registry: str, img: str, secrets: SecretsMgr, transit: str +) -> tuple[int, str, str]: + username, password, transit_mount, transit_key = _get_signing_params( + registry, secrets, transit + ) cmd: CmdArgs = [ "cosign", diff --git a/cbscore/src/cbscore/images/skopeo.py b/cbscore/src/cbscore/images/skopeo.py index b4514987..a00064cd 100644 --- a/cbscore/src/cbscore/images/skopeo.py +++ b/cbscore/src/cbscore/images/skopeo.py @@ -24,7 +24,7 @@ from cbscore.images import get_image_name from cbscore.images import logger as parent_logger from cbscore.images.errors import ImageNotFoundError, SkopeoError -from cbscore.images.signing import sign +from cbscore.images.signing import can_sign, sign from cbscore.utils import CmdArgs, Password, run_cmd from cbscore.utils.containers import get_container_image_base_uri from cbscore.utils.secrets import SecretsMgrError @@ -99,17 +99,20 @@ def skopeo_copy( logger.info(f"copied '{src}' to '{dst}'") - try: - retcode, out, err = sign(dst_registry, dst, secrets, transit) - except SkopeoError as e: - logger.exception(f"error signing image '{dst}'") - raise e # noqa: TRY201 + if can_sign(dst_registry, secrets, transit): + try: + retcode, out, err = sign(dst_registry, dst, secrets, transit) + except SkopeoError as e: + logger.exception(f"error signing image '{dst}'") + raise e # noqa: TRY201 - if retcode != 0: - logger.error(f"error signing image '{dst}': {err}") - raise SkopeoError() + if retcode != 0: + logger.error(f"error signing image '{dst}': {err}") + raise SkopeoError() - logger.info(f"signed image '{dst}': {out}") + logger.info(f"signed image '{dst}': {out}") + else: + logger.warning(f"signing skipped for image '{dst}'") def skopeo_inspect(img: str, secrets: SecretsMgr, *, tls_verify: bool = True) -> str: