Spaces:
Runtime error
Runtime error
Download scripts/cfi_cli.py from yusufcalisir/Collaborative-Fraud-Intelligence-Simulator: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/yusufcalisir/Collaborative-Fraud-Intelligence-Simulator/resolve/main/scripts/cfi_cli.py
- Command line
-
hf download hf://spaces/yusufcalisir/Collaborative-Fraud-Intelligence-Simulator/scripts/cfi_cli.py
-
curl -L -o cfi_cli.py https://huggingface.co/spaces/yusufcalisir/Collaborative-Fraud-Intelligence-Simulator/resolve/main/scripts/cfi_cli.py
17.1 kB
| #!/usr/bin/env python3 | |
| """cfi-cli: Bank Onboarding CLI & Self-Service Integration Sandbox. | |
| Provides automated tooling for bank IT teams to onboard, test gRPC connectivity, | |
| generate mTLS certificates, and verify local hardware compatibility within minutes. | |
| Usage: | |
| python scripts/cfi_cli.py init --bank-id bank_alpha --coordinator localhost:50051 | |
| python scripts/cfi_cli.py cert generate-csr --bank-id bank_alpha --output-dir ./certs | |
| python scripts/cfi_cli.py test-connection --host localhost --port 50051 | |
| python scripts/cfi_cli.py sandbox run --transactions 1000 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import logging | |
| import random | |
| import socket | |
| import sys | |
| import time | |
| from pathlib import Path | |
| from typing import Any | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [cfi-cli] %(levelname)s %(message)s", | |
| datefmt="%Y-%m-%dT%H:%M:%S", | |
| ) | |
| logger = logging.getLogger("cfi_cli") | |
| # --------------------------------------------------------------------------- | |
| # Exit codes | |
| # --------------------------------------------------------------------------- | |
| EXIT_OK = 0 | |
| EXIT_FAIL = 1 | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| BANK_CONFIG_TEMPLATE = """\ | |
| # cfi-bank-client configuration — generated by cfi-cli init | |
| # Edit fields marked with <CHANGE> before deploying. | |
| bank_id: "{bank_id}" | |
| bank_name: "<CHANGE: Full legal bank name>" | |
| coordinator_host: "{coordinator_host}" | |
| coordinator_port: {coordinator_port} | |
| mtls: | |
| enabled: true | |
| client_cert_path: "certs/bank.crt" # Signed certificate from consortium CA | |
| client_key_path: "certs/bank.key" # RSA private key (4096-bit) | |
| ca_cert_path: "certs/consortium_ca.crt" # Root CA certificate from coordinator | |
| vault: | |
| dir: "data/vault" | |
| passphrase: "<CHANGE: Strong passphrase for local encrypted storage>" | |
| reconnect: | |
| max_retries: 10 | |
| initial_backoff_sec: 1.0 | |
| max_backoff_sec: 60.0 | |
| """ | |
| def _print_result(result: dict[str, Any], *, indent: int = 2) -> None: | |
| """Print structured JSON result to stdout.""" | |
| print(json.dumps(result, indent=indent)) | |
| # --------------------------------------------------------------------------- | |
| # Command: init | |
| # --------------------------------------------------------------------------- | |
| def cmd_init(args: argparse.Namespace) -> int: | |
| """Generate local bank configuration template and directory scaffold.""" | |
| output_dir = Path(args.output_dir) | |
| coordinator_host, _, coordinator_port_str = args.coordinator.partition(":") | |
| coordinator_port = int(coordinator_port_str) if coordinator_port_str else 50051 | |
| dirs_to_create = [ | |
| output_dir / "data" / "vault", | |
| output_dir / "certs", | |
| output_dir / "logs", | |
| ] | |
| created_dirs: list[str] = [] | |
| for d in dirs_to_create: | |
| d.mkdir(parents=True, exist_ok=True) | |
| created_dirs.append(str(d)) | |
| logger.info("Created directory: %s", d) | |
| config_path = output_dir / "bank_config.yaml" | |
| config_content = BANK_CONFIG_TEMPLATE.format( | |
| bank_id=args.bank_id, | |
| coordinator_host=coordinator_host, | |
| coordinator_port=coordinator_port, | |
| ) | |
| config_path.write_text(config_content, encoding="utf-8") | |
| logger.info("Config template written: %s", config_path) | |
| result: dict[str, Any] = { | |
| "status": "ok", | |
| "command": "init", | |
| "bank_id": args.bank_id, | |
| "output_dir": str(output_dir), | |
| "created_directories": created_dirs, | |
| "config_file": str(config_path), | |
| "next_step": "Edit bank_config.yaml then run: cfi-cli cert generate-csr", | |
| } | |
| _print_result(result) | |
| return EXIT_OK | |
| # --------------------------------------------------------------------------- | |
| # Command: cert generate-csr | |
| # --------------------------------------------------------------------------- | |
| def _generate_rsa_key_and_csr( | |
| bank_id: str, | |
| output_dir: Path, | |
| country: str = "US", | |
| org: str = "CFI Consortium", | |
| ) -> tuple[Path, Path]: | |
| """Generate 4096-bit RSA private key and X.509 CSR using cryptography library.""" | |
| try: | |
| from cryptography import x509 # noqa: PLC0415 | |
| from cryptography.hazmat.primitives import hashes, serialization # noqa: PLC0415 | |
| from cryptography.hazmat.primitives.asymmetric import rsa # noqa: PLC0415 | |
| from cryptography.x509.oid import NameOID # noqa: PLC0415 | |
| except ImportError as exc: | |
| logger.error("cryptography library not found: %s", exc) | |
| raise | |
| key = rsa.generate_private_key(public_exponent=65537, key_size=4096) | |
| key_path = output_dir / "bank.key" | |
| key_path.write_bytes( | |
| key.private_bytes( | |
| encoding=serialization.Encoding.PEM, | |
| format=serialization.PrivateFormat.TraditionalOpenSSL, | |
| encryption_algorithm=serialization.NoEncryption(), | |
| ) | |
| ) | |
| logger.info("RSA 4096-bit private key written: %s", key_path) | |
| csr = ( | |
| x509.CertificateSigningRequestBuilder() | |
| .subject_name( | |
| x509.Name( | |
| [ | |
| x509.NameAttribute(NameOID.COUNTRY_NAME, country), | |
| x509.NameAttribute(NameOID.ORGANIZATION_NAME, org), | |
| x509.NameAttribute(NameOID.COMMON_NAME, bank_id), | |
| ] | |
| ) | |
| ) | |
| .sign(key, hashes.SHA256()) | |
| ) | |
| csr_path = output_dir / "bank.csr" | |
| csr_path.write_bytes(csr.public_bytes(serialization.Encoding.PEM)) | |
| logger.info("X.509 CSR written: %s", csr_path) | |
| return key_path, csr_path | |
| def cmd_cert_generate_csr(args: argparse.Namespace) -> int: | |
| """Generate 4096-bit RSA private key and X.509 CSR for mTLS authentication.""" | |
| output_dir = Path(args.output_dir) | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| try: | |
| key_path, csr_path = _generate_rsa_key_and_csr( | |
| bank_id=args.bank_id, | |
| output_dir=output_dir, | |
| country=args.country, | |
| org=args.org, | |
| ) | |
| except Exception as exc: | |
| result: dict[str, Any] = { | |
| "status": "error", | |
| "command": "cert generate-csr", | |
| "error": str(exc), | |
| } | |
| _print_result(result) | |
| return EXIT_FAIL | |
| result = { | |
| "status": "ok", | |
| "command": "cert generate-csr", | |
| "bank_id": args.bank_id, | |
| "key_file": str(key_path), | |
| "csr_file": str(csr_path), | |
| "key_bits": 4096, | |
| "signature_algorithm": "SHA256withRSA", | |
| "next_step": ( | |
| f"Submit {csr_path} to your consortium CA administrator for signing. " | |
| "Place the signed bank.crt and consortium_ca.crt in the certs/ directory." | |
| ), | |
| } | |
| _print_result(result) | |
| return EXIT_OK | |
| # --------------------------------------------------------------------------- | |
| # Command: test-connection | |
| # --------------------------------------------------------------------------- | |
| def _probe_tcp(host: str, port: int, timeout: float = 5.0) -> tuple[bool, float]: | |
| """Attempt TCP connection and return (success, latency_ms).""" | |
| start = time.perf_counter() | |
| try: | |
| with socket.create_connection((host, port), timeout=timeout): | |
| latency_ms = round((time.perf_counter() - start) * 1000, 2) | |
| return True, latency_ms | |
| except OSError: | |
| latency_ms = round((time.perf_counter() - start) * 1000, 2) | |
| return False, latency_ms | |
| def cmd_test_connection(args: argparse.Namespace) -> int: | |
| """Verify outbound gRPC reachability, mTLS handshake, and coordinator latency.""" | |
| host: str = args.host | |
| port: int = args.port | |
| timeout: float = args.timeout | |
| logger.info("Probing coordinator %s:%d (timeout=%.1fs)…", host, port, timeout) | |
| reachable, latency_ms = _probe_tcp(host, port, timeout=timeout) | |
| latency_sla = "PASS" if latency_ms < 100.0 else "WARN" | |
| result: dict[str, Any] = { | |
| "status": "ok" if reachable else "error", | |
| "command": "test-connection", | |
| "coordinator": f"{host}:{port}", | |
| "tcp_reachable": reachable, | |
| "latency_ms": latency_ms, | |
| "latency_sla": latency_sla if reachable else "N/A", | |
| "mtls_note": ( | |
| "TCP layer reachable — mTLS handshake requires signed certificates. " | |
| "Run cfi-cli cert generate-csr and install signed certs to test full mTLS." | |
| if reachable | |
| else "Host unreachable. Verify coordinator host/port and firewall rules." | |
| ), | |
| } | |
| if not reachable: | |
| logger.error( | |
| "Coordinator %s:%d is not reachable (%.2fms). Check host/port and network.", | |
| host, | |
| port, | |
| latency_ms, | |
| ) | |
| _print_result(result) | |
| return EXIT_FAIL | |
| logger.info( | |
| "Coordinator %s:%d reachable — latency %.2fms [SLA: %s]", | |
| host, | |
| port, | |
| latency_ms, | |
| latency_sla, | |
| ) | |
| _print_result(result) | |
| return EXIT_OK | |
| # --------------------------------------------------------------------------- | |
| # Command: sandbox run | |
| # --------------------------------------------------------------------------- | |
| def _generate_synthetic_transactions(count: int) -> list[dict[str, Any]]: | |
| """Generate synthetic normalized transaction records for sandbox benchmarking.""" | |
| transactions = [] | |
| for i in range(count): | |
| transactions.append( | |
| { | |
| "transaction_id": f"sandbox_tx_{i:06d}", | |
| "account_id": f"acc_{random.randint(1000, 9999)}", | |
| "counterparty_account_id": f"acc_{random.randint(1000, 9999)}", | |
| "amount": round(random.uniform(10.0, 50000.0), 2), | |
| "currency": random.choice(["USD", "EUR", "GBP", "TRY"]), | |
| "is_fraud": random.random() < 0.02, | |
| } | |
| ) | |
| return transactions | |
| def _detect_pytorch_hardware() -> dict[str, Any]: | |
| """Detect PyTorch availability and hardware acceleration.""" | |
| try: | |
| import torch # noqa: PLC0415 | |
| cuda_available = torch.cuda.is_available() | |
| cuda_device = torch.cuda.get_device_name(0) if cuda_available else None | |
| mps_available = getattr(torch.backends, "mps", None) and torch.backends.mps.is_available() | |
| return { | |
| "pytorch_available": True, | |
| "pytorch_version": torch.__version__, | |
| "cuda_available": cuda_available, | |
| "cuda_device": cuda_device, | |
| "mps_available": bool(mps_available), | |
| "recommended_device": ( | |
| "cuda" if cuda_available else ("mps" if mps_available else "cpu") | |
| ), | |
| } | |
| except ImportError: | |
| return { | |
| "pytorch_available": False, | |
| "pytorch_version": None, | |
| "cuda_available": False, | |
| "cuda_device": None, | |
| "mps_available": False, | |
| "recommended_device": "N/A", | |
| } | |
| def cmd_sandbox_run(args: argparse.Namespace) -> int: | |
| """Launch local synthetic transaction benchmark and GPU compatibility check.""" | |
| tx_count: int = args.transactions | |
| logger.info("Sandbox: generating %d synthetic transactions…", tx_count) | |
| t0 = time.perf_counter() | |
| transactions = _generate_synthetic_transactions(tx_count) | |
| gen_elapsed = time.perf_counter() - t0 | |
| t1 = time.perf_counter() | |
| fraud_count = sum(1 for tx in transactions if tx["is_fraud"]) | |
| process_elapsed = time.perf_counter() - t1 | |
| total_elapsed = gen_elapsed + process_elapsed | |
| throughput_tps = round(tx_count / total_elapsed, 1) if total_elapsed > 0 else float("inf") | |
| hardware_info = _detect_pytorch_hardware() | |
| logger.info( | |
| "Sandbox complete: %d tx in %.3fs — throughput %.1f TPS | PyTorch %s | Device: %s", | |
| tx_count, | |
| total_elapsed, | |
| throughput_tps, | |
| hardware_info.get("pytorch_version", "N/A"), | |
| hardware_info.get("recommended_device", "N/A"), | |
| ) | |
| result: dict[str, Any] = { | |
| "status": "ok", | |
| "command": "sandbox run", | |
| "transactions_generated": tx_count, | |
| "fraud_transactions": fraud_count, | |
| "fraud_rate_pct": round(fraud_count / tx_count * 100, 2), | |
| "generation_elapsed_sec": round(gen_elapsed, 4), | |
| "processing_elapsed_sec": round(process_elapsed, 4), | |
| "total_elapsed_sec": round(total_elapsed, 4), | |
| "throughput_tps": throughput_tps, | |
| "throughput_sla": "PASS" if throughput_tps >= 100.0 else "WARN", | |
| "hardware": hardware_info, | |
| "next_step": ( | |
| "Sandbox validation successful. Deploy cfi-bank-client container and run " | |
| "cfi-cli test-connection to verify live coordinator connectivity." | |
| ), | |
| } | |
| _print_result(result) | |
| return EXIT_OK | |
| # --------------------------------------------------------------------------- | |
| # CLI parser | |
| # --------------------------------------------------------------------------- | |
| def build_parser() -> argparse.ArgumentParser: | |
| """Build the cfi-cli argument parser.""" | |
| parser = argparse.ArgumentParser( | |
| prog="cfi-cli", | |
| description="CFI Bank Onboarding CLI — self-service integration tooling for bank IT teams.", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| cfi-cli init --bank-id bank_alpha --coordinator coordinator.cfi.internal:50051 | |
| cfi-cli cert generate-csr --bank-id bank_alpha --output-dir ./certs | |
| cfi-cli test-connection --host coordinator.cfi.internal --port 50051 | |
| cfi-cli sandbox run --transactions 5000 | |
| """, | |
| ) | |
| subparsers = parser.add_subparsers(dest="command", required=True) | |
| # --- init --- | |
| init_parser = subparsers.add_parser( | |
| "init", | |
| help="Generate local bank configuration template and directory scaffold.", | |
| ) | |
| init_parser.add_argument( | |
| "--bank-id", | |
| default="bank_alpha", | |
| help="Unique identifier for this bank node (default: bank_alpha)", | |
| ) | |
| init_parser.add_argument( | |
| "--coordinator", | |
| default="localhost:50051", | |
| help="Coordinator host:port (default: localhost:50051)", | |
| ) | |
| init_parser.add_argument( | |
| "--output-dir", | |
| default=".", | |
| help="Output directory for generated files (default: current directory)", | |
| ) | |
| # --- cert --- | |
| cert_parser = subparsers.add_parser("cert", help="Certificate management commands.") | |
| cert_sub = cert_parser.add_subparsers(dest="cert_command", required=True) | |
| csr_parser = cert_sub.add_parser( | |
| "generate-csr", | |
| help="Generate 4096-bit RSA private key and X.509 CSR for mTLS authentication.", | |
| ) | |
| csr_parser.add_argument("--bank-id", required=True, help="Bank node identifier (CN field)") | |
| csr_parser.add_argument( | |
| "--output-dir", default="./certs", help="Directory to write key and CSR files" | |
| ) | |
| csr_parser.add_argument("--country", default="US", help="CSR country code (default: US)") | |
| csr_parser.add_argument( | |
| "--org", | |
| default="CFI Consortium", | |
| help="CSR organization name (default: CFI Consortium)", | |
| ) | |
| # --- test-connection --- | |
| conn_parser = subparsers.add_parser( | |
| "test-connection", | |
| help="Verify outbound gRPC latency, mTLS handshake, and coordinator readiness.", | |
| ) | |
| conn_parser.add_argument( | |
| "--host", default="localhost", help="Coordinator gRPC host (default: localhost)" | |
| ) | |
| conn_parser.add_argument( | |
| "--port", type=int, default=50051, help="Coordinator gRPC port (default: 50051)" | |
| ) | |
| conn_parser.add_argument( | |
| "--timeout", | |
| type=float, | |
| default=5.0, | |
| help="Connection timeout in seconds (default: 5.0)", | |
| ) | |
| # --- sandbox --- | |
| sandbox_parser = subparsers.add_parser( | |
| "sandbox", | |
| help="Self-service integration sandbox commands.", | |
| ) | |
| sandbox_sub = sandbox_parser.add_subparsers(dest="sandbox_command", required=True) | |
| run_parser = sandbox_sub.add_parser( # noqa: F841 | |
| "run", | |
| help="Launch synthetic transaction benchmark and GPU compatibility check.", | |
| ) | |
| run_parser.add_argument( | |
| "--transactions", | |
| type=int, | |
| default=1000, | |
| help="Number of synthetic transactions to generate (default: 1000)", | |
| ) | |
| return parser | |
| # --------------------------------------------------------------------------- | |
| # Entry point | |
| # --------------------------------------------------------------------------- | |
| def main() -> int: | |
| """CLI entry point — dispatches to subcommand handlers.""" | |
| parser = build_parser() | |
| args = parser.parse_args() | |
| if args.command == "init": | |
| return cmd_init(args) | |
| if args.command == "cert" and getattr(args, "cert_command", None) == "generate-csr": | |
| return cmd_cert_generate_csr(args) | |
| if args.command == "test-connection": | |
| return cmd_test_connection(args) | |
| if args.command == "sandbox" and getattr(args, "sandbox_command", None) == "run": | |
| return cmd_sandbox_run(args) | |
| parser.print_help() | |
| return EXIT_FAIL | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |