Files
T
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

222 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Log Integrity Chain Agent - Implements SHA-256 hash-chained append-only log for tamper detection."""
import json
import hashlib
import logging
import argparse
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
GENESIS_HASH = "0" * 64
def compute_hash(data):
"""Compute SHA-256 hash of a string."""
return hashlib.sha256(data.encode("utf-8")).hexdigest()
def create_chain_entry(index, timestamp, content, prev_hash):
"""Create a single chain entry with hash linking."""
content_hash = compute_hash(content)
chain_input = f"{prev_hash}{timestamp}{content_hash}"
chain_hash = compute_hash(chain_input)
return {
"index": index,
"timestamp": timestamp,
"content_hash": content_hash,
"prev_hash": prev_hash,
"chain_hash": chain_hash,
"content_preview": content[:200],
}
def load_chain(chain_file):
"""Load an existing hash chain from a JSON file."""
try:
with open(chain_file, "r") as f:
chain = json.load(f)
logger.info("Loaded chain with %d entries from %s", len(chain), chain_file)
return chain
except FileNotFoundError:
logger.info("No existing chain found, starting new chain")
return []
def save_chain(chain, chain_file):
"""Save the hash chain to a JSON file."""
with open(chain_file, "w") as f:
json.dump(chain, f, indent=2)
logger.info("Saved chain with %d entries to %s", len(chain), chain_file)
def ingest_log_file(log_file):
"""Read log entries from a file (one entry per line)."""
entries = []
with open(log_file, "r", errors="ignore") as f:
for line in f:
line = line.strip()
if line:
entries.append(line)
logger.info("Read %d log entries from %s", len(entries), log_file)
return entries
def ingest_json_log(json_file):
"""Read structured log entries from a JSON array file."""
with open(json_file, "r") as f:
data = json.load(f)
entries = []
if isinstance(data, list):
for item in data:
entries.append(json.dumps(item, sort_keys=True))
logger.info("Read %d JSON log entries from %s", len(entries), json_file)
return entries
def append_entries(chain, log_entries):
"""Append new log entries to the hash chain."""
prev_hash = chain[-1]["chain_hash"] if chain else GENESIS_HASH
start_index = len(chain)
new_entries = []
for i, content in enumerate(log_entries):
timestamp = datetime.utcnow().isoformat() + "Z"
entry = create_chain_entry(start_index + i, timestamp, content, prev_hash)
chain.append(entry)
new_entries.append(entry)
prev_hash = entry["chain_hash"]
logger.info("Appended %d entries to chain (total: %d)", len(new_entries), len(chain))
return new_entries
def verify_chain(chain):
"""Verify the integrity of the entire hash chain."""
if not chain:
return {"valid": True, "entries_checked": 0, "breaks": []}
breaks = []
prev_hash = GENESIS_HASH
for entry in chain:
expected_input = f"{prev_hash}{entry['timestamp']}{entry['content_hash']}"
expected_hash = compute_hash(expected_input)
if entry["chain_hash"] != expected_hash:
breaks.append({
"index": entry["index"],
"expected_hash": expected_hash,
"actual_hash": entry["chain_hash"],
"prev_hash_match": entry["prev_hash"] == prev_hash,
})
if entry["prev_hash"] != prev_hash:
breaks.append({
"index": entry["index"],
"issue": "prev_hash mismatch",
"expected_prev": prev_hash,
"actual_prev": entry["prev_hash"],
})
prev_hash = entry["chain_hash"]
valid = len(breaks) == 0
logger.info("Chain verification: %d entries checked, %d breaks found", len(chain), len(breaks))
return {"valid": valid, "entries_checked": len(chain), "breaks": breaks}
def find_tampered_range(breaks):
"""Identify the range of entries affected by tampering."""
if not breaks:
return None
first_break = min(b["index"] for b in breaks)
return {
"first_tampered_entry": first_break,
"total_affected": len(breaks),
"tamper_start_index": first_break,
"note": f"All entries from index {first_break} onward may be compromised",
}
def create_checkpoint(chain, checkpoint_file):
"""Create an integrity checkpoint with the current chain head hash."""
if not chain:
return None
checkpoint = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"chain_length": len(chain),
"head_hash": chain[-1]["chain_hash"],
"head_index": chain[-1]["index"],
"genesis_hash": GENESIS_HASH,
"checkpoint_hash": compute_hash(f"{len(chain)}{chain[-1]['chain_hash']}"),
}
with open(checkpoint_file, "w") as f:
json.dump(checkpoint, f, indent=2)
logger.info("Created checkpoint at index %d: %s", checkpoint["head_index"], checkpoint["checkpoint_hash"][:16])
return checkpoint
def verify_checkpoint(chain, checkpoint_file):
"""Verify chain against a previously saved checkpoint."""
with open(checkpoint_file, "r") as f:
checkpoint = json.load(f)
cp_index = checkpoint["head_index"]
if cp_index >= len(chain):
return {"valid": False, "error": "Chain shorter than checkpoint"}
actual_hash = chain[cp_index]["chain_hash"]
valid = actual_hash == checkpoint["head_hash"]
return {
"valid": valid,
"checkpoint_index": cp_index,
"expected_hash": checkpoint["head_hash"],
"actual_hash": actual_hash,
}
def generate_report(verification, checkpoint, chain_length):
"""Generate log integrity verification report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"chain_length": chain_length,
"integrity_valid": verification["valid"],
"entries_checked": verification["entries_checked"],
"breaks_found": len(verification["breaks"]),
"break_details": verification["breaks"][:20],
"checkpoint": checkpoint,
}
status = "INTACT" if verification["valid"] else "TAMPERED"
print(f"LOG INTEGRITY: {status} - {chain_length} entries, {len(verification['breaks'])} breaks")
return report
def main():
parser = argparse.ArgumentParser(description="Log Integrity Chain Agent")
parser.add_argument("--log-file", help="Log file to ingest")
parser.add_argument("--chain-file", default="log_chain.json", help="Hash chain storage file")
parser.add_argument("--verify", action="store_true", help="Verify chain integrity")
parser.add_argument("--checkpoint", help="Create/verify checkpoint file")
parser.add_argument("--output", default="integrity_report.json")
args = parser.parse_args()
chain = load_chain(args.chain_file)
if args.log_file:
entries = ingest_log_file(args.log_file)
append_entries(chain, entries)
save_chain(chain, args.chain_file)
verification = {"valid": True, "entries_checked": 0, "breaks": []}
if args.verify:
verification = verify_chain(chain)
checkpoint = None
if args.checkpoint:
if args.verify:
checkpoint = verify_checkpoint(chain, args.checkpoint)
else:
checkpoint = create_checkpoint(chain, args.checkpoint)
report = generate_report(verification, checkpoint, len(chain))
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()