mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 06:04:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
194 lines
7.0 KiB
Python
194 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for securing remote access to OT environments.
|
|
|
|
Manages remote access sessions with MFA verification, session
|
|
recording, role-based access policies, vendor co-attendance
|
|
requirements, and CIP-005 compliance auditing.
|
|
"""
|
|
|
|
import json
|
|
import hashlib
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
|
|
|
|
class SessionState(str, Enum):
|
|
PENDING = "pending_approval"
|
|
APPROVED = "approved"
|
|
ACTIVE = "active"
|
|
TERMINATED = "terminated"
|
|
EXPIRED = "expired"
|
|
DENIED = "denied"
|
|
|
|
|
|
class UserRole(str, Enum):
|
|
OT_OPERATOR = "ot_operator"
|
|
OT_ENGINEER = "ot_engineer"
|
|
VENDOR = "vendor"
|
|
SECURITY = "security_analyst"
|
|
|
|
|
|
class OTRemoteAccessAgent:
|
|
"""Manages secure remote access to OT environments."""
|
|
|
|
def __init__(self, output_dir="./ot_remote_access"):
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.sessions = {}
|
|
self.policies = {}
|
|
self.audit_log = []
|
|
|
|
def define_policy(self, role, allowed_targets, protocols, max_minutes):
|
|
"""Define access policy for a user role."""
|
|
self.policies[role] = {
|
|
"allowed_targets": allowed_targets,
|
|
"protocols": protocols,
|
|
"max_duration_minutes": max_minutes,
|
|
"requires_approval": role == UserRole.VENDOR,
|
|
"requires_co_attendance": role == UserRole.VENDOR,
|
|
}
|
|
|
|
def request_session(self, user_id, role, source_ip, target, target_ip,
|
|
protocol, purpose):
|
|
"""Request a new remote access session."""
|
|
sid = hashlib.sha256(
|
|
f"{user_id}{target_ip}{datetime.utcnow().isoformat()}".encode()
|
|
).hexdigest()[:16]
|
|
|
|
policy = self.policies.get(role)
|
|
if not policy:
|
|
self._log("DENIED", user_id, target, "No policy for role")
|
|
return None, "No policy defined for role"
|
|
|
|
if target not in policy["allowed_targets"]:
|
|
self._log("DENIED", user_id, target, "Target not authorized")
|
|
return None, f"Target {target} not authorized for {role}"
|
|
|
|
if protocol not in policy["protocols"]:
|
|
self._log("DENIED", user_id, target, f"Protocol {protocol} not allowed")
|
|
return None, f"Protocol {protocol} not authorized"
|
|
|
|
session = {
|
|
"session_id": sid,
|
|
"user_id": user_id,
|
|
"role": role,
|
|
"source_ip": source_ip,
|
|
"target": target,
|
|
"target_ip": target_ip,
|
|
"protocol": protocol,
|
|
"purpose": purpose,
|
|
"state": SessionState.PENDING if policy["requires_approval"] else SessionState.APPROVED,
|
|
"mfa_verified": False,
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"max_duration": policy["max_duration_minutes"],
|
|
}
|
|
self.sessions[sid] = session
|
|
self._log("REQUESTED", user_id, target, purpose)
|
|
return sid, "Session created"
|
|
|
|
def approve_session(self, sid, approver_id):
|
|
"""Approve a pending session."""
|
|
s = self.sessions.get(sid)
|
|
if not s or s["state"] != SessionState.PENDING:
|
|
return False, "Session not pending"
|
|
s["state"] = SessionState.APPROVED
|
|
s["approved_by"] = approver_id
|
|
self._log("APPROVED", approver_id, s["target"], f"Session {sid}")
|
|
return True, "Approved"
|
|
|
|
def activate_session(self, sid, mfa_verified=True):
|
|
"""Activate session after MFA verification."""
|
|
s = self.sessions.get(sid)
|
|
if not s or s["state"] != SessionState.APPROVED:
|
|
return False, "Not approved"
|
|
s["state"] = SessionState.ACTIVE
|
|
s["mfa_verified"] = mfa_verified
|
|
s["started_at"] = datetime.utcnow().isoformat()
|
|
s["recording_path"] = f"/recordings/{sid}_{datetime.utcnow().strftime('%Y%m%d')}.mp4"
|
|
self._log("ACTIVATED", s["user_id"], s["target"], f"MFA={'OK' if mfa_verified else 'FAIL'}")
|
|
return True, "Active"
|
|
|
|
def terminate_session(self, sid, reason="manual"):
|
|
"""Terminate an active session."""
|
|
s = self.sessions.get(sid)
|
|
if not s:
|
|
return False
|
|
s["state"] = SessionState.TERMINATED
|
|
s["ended_at"] = datetime.utcnow().isoformat()
|
|
self._log("TERMINATED", s["user_id"], s["target"], reason)
|
|
return True
|
|
|
|
def check_expired(self):
|
|
"""Find and terminate expired sessions."""
|
|
expired = []
|
|
now = datetime.utcnow()
|
|
for sid, s in self.sessions.items():
|
|
if s["state"] != SessionState.ACTIVE:
|
|
continue
|
|
started = datetime.fromisoformat(s["started_at"])
|
|
if (now - started).total_seconds() > s["max_duration"] * 60:
|
|
self.terminate_session(sid, "max_duration_exceeded")
|
|
expired.append(sid)
|
|
return expired
|
|
|
|
def audit_compliance(self):
|
|
"""Check CIP-005 remote access compliance."""
|
|
issues = []
|
|
for sid, s in self.sessions.items():
|
|
if s["state"] == SessionState.ACTIVE and not s["mfa_verified"]:
|
|
issues.append({"session": sid, "issue": "Active session without MFA (CIP-005-7 R2.4)"})
|
|
if s["role"] == UserRole.VENDOR:
|
|
policy = self.policies.get(UserRole.VENDOR, {})
|
|
if policy.get("requires_co_attendance") and "co_attendant" not in s:
|
|
issues.append({"session": sid, "issue": "Vendor session without co-attendance"})
|
|
return issues
|
|
|
|
def _log(self, event, user, target, detail):
|
|
self.audit_log.append({
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"event": event,
|
|
"user": user,
|
|
"target": target,
|
|
"detail": detail,
|
|
})
|
|
|
|
def generate_report(self):
|
|
compliance = self.audit_compliance()
|
|
active = [s for s in self.sessions.values() if s["state"] == SessionState.ACTIVE]
|
|
|
|
report = {
|
|
"report_date": datetime.utcnow().isoformat(),
|
|
"total_sessions": len(self.sessions),
|
|
"active_sessions": len(active),
|
|
"compliance_issues": compliance,
|
|
"sessions": list(self.sessions.values()),
|
|
"audit_log": self.audit_log[-50:],
|
|
}
|
|
out = self.output_dir / "ot_remote_access_report.json"
|
|
with open(out, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(json.dumps(report, indent=2))
|
|
return report
|
|
|
|
|
|
def main():
|
|
agent = OTRemoteAccessAgent()
|
|
agent.define_policy(UserRole.OT_ENGINEER, ["HMI-01", "EWS-01", "HISTORIAN-01"],
|
|
["RDP", "SSH"], 240)
|
|
agent.define_policy(UserRole.VENDOR, ["DCS-EWS-01"], ["RDP"], 120)
|
|
|
|
sid, msg = agent.request_session("vendor_01", UserRole.VENDOR,
|
|
"203.0.113.50", "DCS-EWS-01",
|
|
"10.30.1.20", "RDP",
|
|
"DCS firmware update CR-2026-0045")
|
|
if sid:
|
|
agent.approve_session(sid, "ot_manager_01")
|
|
agent.activate_session(sid)
|
|
|
|
agent.generate_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|