src/pqc_ebpf_attestation/cli.py
| 1 | """Command-line interface for pqc-ebpf-attestation.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import json |
| 6 | import sys |
| 7 | |
| 8 | import click |
| 9 | from quantumshield.identity.agent import AgentIdentity |
| 10 | |
| 11 | from pqc_ebpf_attestation import __version__ |
| 12 | from pqc_ebpf_attestation.program import BPFProgram, BPFProgramMetadata, BPFProgramType |
| 13 | from pqc_ebpf_attestation.signer import BPFSigner, BPFVerifier, SignedBPFProgram |
| 14 | |
| 15 | |
| 16 | @click.group() |
| 17 | @click.version_option(version=__version__, prog_name="pqc-bpf") |
| 18 | def main() -> None: |
| 19 | """pqc-bpf - sign and verify eBPF programs with ML-DSA.""" |
| 20 | |
| 21 | |
| 22 | @main.command() |
| 23 | @click.argument("bpf_file", type=click.Path(exists=True)) |
| 24 | @click.option("--name", required=True, help="Program name") |
| 25 | @click.option( |
| 26 | "--type", |
| 27 | "program_type", |
| 28 | type=click.Choice([t.value for t in BPFProgramType]), |
| 29 | default=BPFProgramType.KPROBE.value, |
| 30 | ) |
| 31 | @click.option("--author", default="") |
| 32 | @click.option("--identity-name", default="bpf-signer", help="Name of the signing identity") |
| 33 | @click.option( |
| 34 | "--output", |
| 35 | "-o", |
| 36 | type=click.Path(), |
| 37 | default=None, |
| 38 | help="Write signed envelope JSON to file (default: <bpf_file>.sig.json)", |
| 39 | ) |
| 40 | def sign( |
| 41 | bpf_file: str, |
| 42 | name: str, |
| 43 | program_type: str, |
| 44 | author: str, |
| 45 | identity_name: str, |
| 46 | output: str | None, |
| 47 | ) -> None: |
| 48 | """Sign an eBPF program file.""" |
| 49 | metadata = BPFProgramMetadata( |
| 50 | name=name, |
| 51 | program_type=BPFProgramType(program_type), |
| 52 | author=author, |
| 53 | ) |
| 54 | program = BPFProgram.from_file(metadata, bpf_file) |
| 55 | |
| 56 | identity = AgentIdentity.create(identity_name) |
| 57 | signer = BPFSigner(identity) |
| 58 | signed = signer.sign(program) |
| 59 | |
| 60 | out_path = output or f"{bpf_file}.sig.json" |
| 61 | with open(out_path, "w", encoding="utf-8") as f: |
| 62 | json.dump(signed.to_dict(), f, indent=2) |
| 63 | |
| 64 | click.echo(f"Signed {bpf_file} -> {out_path}") |
| 65 | click.echo(f" signer_did: {signed.signer_did}") |
| 66 | click.echo(f" algorithm: {signed.algorithm}") |
| 67 | click.echo(f" bytecode_hash: {signed.program.bytecode_hash}") |
| 68 | |
| 69 | |
| 70 | @main.command() |
| 71 | @click.argument("signed_json", type=click.Path(exists=True)) |
| 72 | def verify(signed_json: str) -> None: |
| 73 | """Verify a signed eBPF envelope.""" |
| 74 | with open(signed_json, encoding="utf-8") as f: |
| 75 | data = json.load(f) |
| 76 | signed = SignedBPFProgram.from_dict(data) |
| 77 | result = BPFVerifier.verify(signed) |
| 78 | if result.valid: |
| 79 | click.echo(f"[OK] {signed.program.metadata.name} - signature VALID") |
| 80 | click.echo(f" signer_did: {signed.signer_did}") |
| 81 | click.echo(f" program_type: {signed.program.metadata.program_type.value}") |
| 82 | click.echo(f" bytecode_hash: {signed.program.bytecode_hash}") |
| 83 | sys.exit(0) |
| 84 | else: |
| 85 | click.echo(f"[FAIL] {signed.program.metadata.name} - {result.error}", err=True) |
| 86 | sys.exit(1) |
| 87 | |
| 88 | |
| 89 | @main.command() |
| 90 | @click.argument("signed_json", type=click.Path(exists=True)) |
| 91 | def info(signed_json: str) -> None: |
| 92 | """Show metadata about a signed envelope.""" |
| 93 | with open(signed_json, encoding="utf-8") as f: |
| 94 | data = json.load(f) |
| 95 | signed = SignedBPFProgram.from_dict(data) |
| 96 | click.echo(f"program name: {signed.program.metadata.name}") |
| 97 | click.echo(f"program type: {signed.program.metadata.program_type.value}") |
| 98 | click.echo(f"author: {signed.program.metadata.author}") |
| 99 | click.echo(f"description: {signed.program.metadata.description}") |
| 100 | click.echo(f"kernel min: {signed.program.metadata.kernel_min}") |
| 101 | click.echo(f"attach point: {signed.program.metadata.attach_point}") |
| 102 | click.echo(f"license: {signed.program.metadata.license}") |
| 103 | click.echo(f"bytecode hash: {signed.program.bytecode_hash}") |
| 104 | click.echo(f"bytecode size: {signed.program.bytecode_size} bytes") |
| 105 | click.echo(f"signer_did: {signed.signer_did}") |
| 106 | click.echo(f"algorithm: {signed.algorithm}") |
| 107 | click.echo(f"signed_at: {signed.signed_at}") |
| 108 | |
| 109 | |
| 110 | if __name__ == "__main__": |
| 111 | main() |
| 112 | |