import json import logging import os import subprocess import tempfile import typer from id_tools.config import SourceConfig app = typer.Typer( help="Publish the documentation and release announcements to external destinations." ) logger = logging.getLogger(__name__) @app.command(name="sign-docker", help="Signe l'image Docker sur le registre") def sign_docker_image( registry: str = typer.Option( "interdiode", help="Registre cible pour l'image Docker" ), docker_login: str = typer.Option( "", envvar="DOCKER_HUB_LOGIN", help="Nom d'utilisateur Docker" ), docker_token: str = typer.Option( "", envvar="DOCKER_HUB_TOKEN", help="Jeton d'accès Docker" ), cosign_password: str = typer.Option( "", envvar="COSIGN_PASSWORD", help="Mot de passe de la clé privée Cosign" ), cosign_private_key: str = typer.Option( "", envvar="COSIGN_PRIVATE_KEY", help="Contenu de la clé privée Cosign" ), ): config = SourceConfig() version = config.get_project_version() image = config.get_project_name() v_tag = f"{image}:{version}" l_tag = f"{image}:latest" destinations = [f"{registry}/{v_tag}", f"{registry}/{l_tag}"] if docker_login and docker_token: cmd = ["docker", "login", "-u", docker_login, "--password-stdin"] subprocess.run( cmd, input=docker_token, text=True, check=True, cwd=config.source_dir ) digest_references = { get_reference_from_tag(destination) for destination in destinations } if cosign_password and cosign_private_key: with ( tempfile.NamedTemporaryFile(mode="w", suffix=".key", delete=True) as key_fd, tempfile.TemporaryDirectory() as tmp_dir, ): key_fd.write(cosign_private_key) key_fd.flush() cosign_env = {**os.environ, "COSIGN_PASSWORD": cosign_password} trivy_report = os.path.join(tmp_dir, "trivy_report.json") for digest_reference in digest_references: # noinspection SpellCheckingInspection cmd = [ "trivy", "image", "--format", "json", "--output", trivy_report, digest_reference, ] subprocess.run(cmd, text=True, check=True, cwd=config.source_dir) cmd = [ "cosign", "attest", "--key", key_fd.name, "--type", "vuln", "--predicate", trivy_report, digest_reference, ] subprocess.run(cmd, text=True, check=True, cwd=config.source_dir) cmd = [ "cosign", "sign", "--yes", "--recursive", "--key", key_fd.name, digest_reference, ] subprocess.check_call(cmd, env=cosign_env) cmd = [ "cosign", "verify-attestation", "--key", config.cosign_key_url, "--type", "vuln", digest_reference, ] subprocess.check_call(cmd, env=cosign_env) cmd = [ "cosign", "verify", "--key", config.cosign_key_url, digest_reference, ] subprocess.check_call(cmd, env=cosign_env) typer.echo(f"✓ Image signée : {registry}/{v_tag} et {registry}/{l_tag}") def get_reference_from_tag(destination: str) -> str: # noinspection SpellCheckingInspection cmd = [ "docker", "buildx", "imagetools", "inspect", destination, "--format", "{{json .Manifest}}", ] result = subprocess.run(cmd, check=True, capture_output=True, text=True) manifest = json.loads(result.stdout) image_name = destination.rsplit(":", 1)[0] reference = f"{image_name}@{manifest['digest']}" return reference