feat(tyr): implement eval_tyr_identity and get_tyr_user_risk tools
This commit is contained in:
parent
816bfc151b
commit
9022486e8a
|
|
@ -29,6 +29,7 @@ This document tracks the high-level goals and future development milestones for
|
||||||
|
|
||||||
## Phase 5: OISSU Loop & Custom MCP Security Tools
|
## Phase 5: OISSU Loop & Custom MCP Security Tools
|
||||||
- [x] Build OISSU forecast and anomaly detection tools.
|
- [x] Build OISSU forecast and anomaly detection tools.
|
||||||
|
- [x] Build `eval_tyr_identity` and `get_tyr_user_risk` tools.
|
||||||
- [ ] 5.2 Build `eval_tyr_identity` and `get_tyr_user_risk` tools.
|
- [ ] 5.2 Build `eval_tyr_identity` and `get_tyr_user_risk` tools.
|
||||||
- [ ] 5.3 Build `scan_tyr_surface` and `attest_tyr_supply_chain` tools.
|
- [ ] 5.3 Build `scan_tyr_surface` and `attest_tyr_supply_chain` tools.
|
||||||
- [ ] 5.4 Build `run_tyr_response` tool for automated threat containment.
|
- [ ] 5.4 Build `run_tyr_response` tool for automated threat containment.
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,7 @@
|
||||||
- **Task 5.1: Implement OISSU Tools**
|
- **Task 5.1: Implement OISSU Tools**
|
||||||
- Status: **Complete**
|
- Status: **Complete**
|
||||||
- Notes: Implemented initial logic for `get_tyr_forecast` and `scan_tyr_surface` tools.
|
- Notes: Implemented initial logic for `get_tyr_forecast` and `scan_tyr_surface` tools.
|
||||||
|
|
||||||
|
- **Task 5.2: Implement Identity & Risk Tools**
|
||||||
|
- Status: **Complete**
|
||||||
|
- Notes: Implemented `eval_tyr_identity` and `get_tyr_user_risk` tools.
|
||||||
|
|
|
||||||
31
tyr/tools/eval_tyr_identity.py
Normal file
31
tyr/tools/eval_tyr_identity.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# tyr/tools/eval_tyr_identity.py - MCP Tool for verifying SPIFFE identities
|
||||||
|
#
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def eval_tyr_identity(p: dict) -> dict:
|
||||||
|
"""Verifies the validity and properties of a SPIFFE SVID by querying the SPIRE server."""
|
||||||
|
spiffe_id = p.get("spiffe_id")
|
||||||
|
if not spiffe_id:
|
||||||
|
raise ValueError("Missing required parameter: 'spiffe_id'")
|
||||||
|
|
||||||
|
if not spiffe_id.startswith("spiffe://vauco.no/"):
|
||||||
|
return {"status": "FAIL", "reason": "Invalid SPIFFE ID format for trust domain 'vauco.no'."}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# This assumes the spire-server binary is in the path or at this location
|
||||||
|
cmd = ["./spire-1.15.3/bin/spire-server", "entry", "show", "-spiffeID", spiffe_id]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=10)
|
||||||
|
|
||||||
|
if "Found 1 entry" in result.stdout:
|
||||||
|
# A more robust check would parse the TTL and other details
|
||||||
|
return {"status": "PASS", "spiffe_id": spiffe_id, "message": "Entry found and is active."}
|
||||||
|
else:
|
||||||
|
return {"status": "FAIL", "spiffe_id": spiffe_id, "reason": "SPIFFE ID not found or has no active entry."}
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# This handles cases where the entry is not found, as spire-server returns a non-zero exit code
|
||||||
|
return {"status": "FAIL", "spiffe_id": spiffe_id, "reason": f"SPIRE server error: Entry not found."}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "ERROR", "message": str(e)}
|
||||||
77
tyr/tools/get_tyr_user_risk.py
Normal file
77
tyr/tools/get_tyr_user_risk.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# tyr/tools/get_tyr_user_risk.py - MCP Tool for calculating user risk scores
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
from google.cloud import bigquery
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
RISK_WEIGHTS = {
|
||||||
|
"google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion": 10,
|
||||||
|
"google.iam.admin.v1.SetIamPolicy": 20,
|
||||||
|
"google.storage.v1.Storage.DeleteBucket": 25,
|
||||||
|
"google.login.LoginService.LoginFailure": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_tyr_user_risk(p: dict) -> dict:
|
||||||
|
"""Calculates a risk score for a user based on recent audit log activity."""
|
||||||
|
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT", "propane-will-491900-m5")
|
||||||
|
principal_email = p.get("principal_email")
|
||||||
|
caller_ip = p.get("caller_ip")
|
||||||
|
hours = p.get("hours", 24)
|
||||||
|
|
||||||
|
if not (principal_email or caller_ip):
|
||||||
|
raise ValueError("Missing required parameter: 'principal_email' or 'caller_ip'")
|
||||||
|
|
||||||
|
client = bigquery.Client()
|
||||||
|
table_id = f"{project_id}.tyr_audit_logs.cloudaudit_googleapis_com_activity"
|
||||||
|
|
||||||
|
where_clauses = [f"timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL {hours} HOUR)"]
|
||||||
|
if principal_email:
|
||||||
|
where_clauses.append(f"protopayload_auditlog.authenticationInfo.principalEmail = '{principal_email}'")
|
||||||
|
if caller_ip:
|
||||||
|
where_clauses.append(f"protopayload_auditlog.requestMetadata.callerIp = '{caller_ip}'")
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
timestamp,
|
||||||
|
protopayload_auditlog.methodName,
|
||||||
|
protopayload_auditlog.status.message as status_message
|
||||||
|
FROM `{table_id}`
|
||||||
|
WHERE {" AND ".join(where_clauses)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
query_job = client.query(query)
|
||||||
|
results = query_job.result()
|
||||||
|
|
||||||
|
risk_score = 0
|
||||||
|
findings = []
|
||||||
|
|
||||||
|
for row in results:
|
||||||
|
weight = RISK_WEIGHTS.get(row.methodName, 0) # Default to 0 for non-risky methods
|
||||||
|
if row.status_message and "authentication failed" in row.status_message.lower():
|
||||||
|
weight = RISK_WEIGHTS.get("google.login.LoginService.LoginFailure", 5)
|
||||||
|
|
||||||
|
if weight > 0:
|
||||||
|
risk_score += weight
|
||||||
|
findings.append({
|
||||||
|
"timestamp": row.timestamp.isoformat(),
|
||||||
|
"method": row.methodName,
|
||||||
|
"status": row.status_message,
|
||||||
|
"weight": weight
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"principal_email": principal_email,
|
||||||
|
"caller_ip": caller_ip,
|
||||||
|
"time_window_hours": hours,
|
||||||
|
"calculated_risk_score": risk_score,
|
||||||
|
"findings_count": len(findings),
|
||||||
|
"findings": findings
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred while querying audit logs: {e}")
|
||||||
|
raise
|
||||||
Loading…
Reference in New Issue
Block a user