diagram-to-iac 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diagram_to_iac/__init__.py +10 -0
- diagram_to_iac/actions/__init__.py +7 -0
- diagram_to_iac/actions/git_entry.py +174 -0
- diagram_to_iac/actions/supervisor_entry.py +116 -0
- diagram_to_iac/actions/terraform_agent_entry.py +207 -0
- diagram_to_iac/agents/__init__.py +26 -0
- diagram_to_iac/agents/demonstrator_langgraph/__init__.py +10 -0
- diagram_to_iac/agents/demonstrator_langgraph/agent.py +826 -0
- diagram_to_iac/agents/git_langgraph/__init__.py +10 -0
- diagram_to_iac/agents/git_langgraph/agent.py +1018 -0
- diagram_to_iac/agents/git_langgraph/pr.py +146 -0
- diagram_to_iac/agents/hello_langgraph/__init__.py +9 -0
- diagram_to_iac/agents/hello_langgraph/agent.py +621 -0
- diagram_to_iac/agents/policy_agent/__init__.py +15 -0
- diagram_to_iac/agents/policy_agent/agent.py +507 -0
- diagram_to_iac/agents/policy_agent/integration_example.py +191 -0
- diagram_to_iac/agents/policy_agent/tools/__init__.py +14 -0
- diagram_to_iac/agents/policy_agent/tools/tfsec_tool.py +259 -0
- diagram_to_iac/agents/shell_langgraph/__init__.py +21 -0
- diagram_to_iac/agents/shell_langgraph/agent.py +122 -0
- diagram_to_iac/agents/shell_langgraph/detector.py +50 -0
- diagram_to_iac/agents/supervisor_langgraph/__init__.py +17 -0
- diagram_to_iac/agents/supervisor_langgraph/agent.py +1947 -0
- diagram_to_iac/agents/supervisor_langgraph/demonstrator.py +22 -0
- diagram_to_iac/agents/supervisor_langgraph/guards.py +23 -0
- diagram_to_iac/agents/supervisor_langgraph/pat_loop.py +49 -0
- diagram_to_iac/agents/supervisor_langgraph/router.py +9 -0
- diagram_to_iac/agents/terraform_langgraph/__init__.py +15 -0
- diagram_to_iac/agents/terraform_langgraph/agent.py +1216 -0
- diagram_to_iac/agents/terraform_langgraph/parser.py +76 -0
- diagram_to_iac/core/__init__.py +7 -0
- diagram_to_iac/core/agent_base.py +19 -0
- diagram_to_iac/core/enhanced_memory.py +302 -0
- diagram_to_iac/core/errors.py +4 -0
- diagram_to_iac/core/issue_tracker.py +49 -0
- diagram_to_iac/core/memory.py +132 -0
- diagram_to_iac/r2d.py +345 -13
- diagram_to_iac/services/__init__.py +10 -0
- diagram_to_iac/services/observability.py +59 -0
- diagram_to_iac/services/step_summary.py +77 -0
- diagram_to_iac/tools/__init__.py +11 -0
- diagram_to_iac/tools/api_utils.py +108 -26
- diagram_to_iac/tools/git/__init__.py +45 -0
- diagram_to_iac/tools/git/git.py +956 -0
- diagram_to_iac/tools/hello/__init__.py +30 -0
- diagram_to_iac/tools/hello/cal_utils.py +31 -0
- diagram_to_iac/tools/hello/text_utils.py +97 -0
- diagram_to_iac/tools/llm_utils/__init__.py +20 -0
- diagram_to_iac/tools/llm_utils/anthropic_driver.py +87 -0
- diagram_to_iac/tools/llm_utils/base_driver.py +90 -0
- diagram_to_iac/tools/llm_utils/gemini_driver.py +89 -0
- diagram_to_iac/tools/llm_utils/openai_driver.py +93 -0
- diagram_to_iac/tools/llm_utils/router.py +303 -0
- diagram_to_iac/tools/sec_utils.py +4 -2
- diagram_to_iac/tools/shell/__init__.py +17 -0
- diagram_to_iac/tools/shell/shell.py +415 -0
- diagram_to_iac/tools/text_utils.py +277 -0
- diagram_to_iac/tools/tf/terraform.py +851 -0
- diagram_to_iac-0.9.0.dist-info/METADATA +256 -0
- diagram_to_iac-0.9.0.dist-info/RECORD +64 -0
- {diagram_to_iac-0.7.0.dist-info → diagram_to_iac-0.9.0.dist-info}/WHEEL +1 -1
- diagram_to_iac-0.9.0.dist-info/entry_points.txt +6 -0
- diagram_to_iac/agents/codegen_agent.py +0 -0
- diagram_to_iac/agents/consensus_agent.py +0 -0
- diagram_to_iac/agents/deployment_agent.py +0 -0
- diagram_to_iac/agents/github_agent.py +0 -0
- diagram_to_iac/agents/interpretation_agent.py +0 -0
- diagram_to_iac/agents/question_agent.py +0 -0
- diagram_to_iac/agents/supervisor.py +0 -0
- diagram_to_iac/agents/vision_agent.py +0 -0
- diagram_to_iac/core/config.py +0 -0
- diagram_to_iac/tools/cv_utils.py +0 -0
- diagram_to_iac/tools/gh_utils.py +0 -0
- diagram_to_iac/tools/tf_utils.py +0 -0
- diagram_to_iac-0.7.0.dist-info/METADATA +0 -16
- diagram_to_iac-0.7.0.dist-info/RECORD +0 -32
- diagram_to_iac-0.7.0.dist-info/entry_points.txt +0 -2
- {diagram_to_iac-0.7.0.dist-info → diagram_to_iac-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,507 @@
|
|
1
|
+
"""
|
2
|
+
Policy Agent - Security Policy Enforcement for Terraform
|
3
|
+
|
4
|
+
This PolicyAgent enforces security policies using tfsec scanning and provides
|
5
|
+
policy gate functionality to block terraform apply operations on critical violations.
|
6
|
+
It integrates with the existing multi-agent architecture following established patterns.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- tfsec security scanning with JSON output
|
10
|
+
- Policy evaluation and apply blocking on critical violations
|
11
|
+
- JSON findings artifacts for audit trails
|
12
|
+
- Integration with LangGraph multi-agent workflow
|
13
|
+
- Follows established security patterns with ShellExecutor
|
14
|
+
|
15
|
+
Architecture:
|
16
|
+
1. LLM-based planner analyzes input and routes to appropriate nodes
|
17
|
+
2. Policy scan node executes tfsec scans using secure shell execution
|
18
|
+
3. Policy evaluation node determines if violations should block operations
|
19
|
+
4. Artifact creation node posts JSON findings for audit trails
|
20
|
+
5. Block node prevents terraform apply on critical violations
|
21
|
+
"""
|
22
|
+
|
23
|
+
import os
|
24
|
+
import time
|
25
|
+
import uuid
|
26
|
+
import logging
|
27
|
+
from datetime import datetime
|
28
|
+
from pathlib import Path
|
29
|
+
from typing import TypedDict, Annotated, Optional, List, Dict, Any
|
30
|
+
|
31
|
+
import yaml
|
32
|
+
from langchain_core.messages import HumanMessage, BaseMessage
|
33
|
+
from langgraph.graph import StateGraph, END
|
34
|
+
from langgraph.checkpoint.memory import MemorySaver
|
35
|
+
from pydantic import BaseModel, Field
|
36
|
+
|
37
|
+
# Import core infrastructure
|
38
|
+
from diagram_to_iac.core.agent_base import AgentBase
|
39
|
+
from diagram_to_iac.core.memory import create_memory, LangGraphMemoryAdapter
|
40
|
+
from .tools.tfsec_tool import TfSecTool, TfSecScanInput, TfSecScanOutput
|
41
|
+
|
42
|
+
|
43
|
+
# --- Pydantic Schemas for Agent I/O ---
|
44
|
+
class PolicyAgentInput(BaseModel):
|
45
|
+
"""Input schema for PolicyAgent operations."""
|
46
|
+
query: str = Field(..., description="Policy enforcement query or command")
|
47
|
+
repo_path: str = Field(..., description="Path to Terraform repository")
|
48
|
+
thread_id: Optional[str] = Field(default=None, description="Thread ID for conversation tracking")
|
49
|
+
operation_type: str = Field(default="scan", description="Type of policy operation (scan, evaluate, block)")
|
50
|
+
|
51
|
+
|
52
|
+
class PolicyAgentOutput(BaseModel):
|
53
|
+
"""Output schema for PolicyAgent operations."""
|
54
|
+
result: str = Field(..., description="Policy enforcement result")
|
55
|
+
thread_id: str = Field(..., description="Thread ID for conversation tracking")
|
56
|
+
policy_status: str = Field(..., description="Policy status (PASS, BLOCK, ERROR)")
|
57
|
+
findings_count: int = Field(default=0, description="Number of policy findings")
|
58
|
+
critical_findings: int = Field(default=0, description="Number of critical findings")
|
59
|
+
should_block_apply: bool = Field(default=False, description="Whether to block terraform apply")
|
60
|
+
artifact_path: Optional[str] = Field(default=None, description="Path to findings artifact")
|
61
|
+
error_message: Optional[str] = Field(default=None, description="Error message if operation failed")
|
62
|
+
|
63
|
+
|
64
|
+
# --- LangGraph State Schema ---
|
65
|
+
class PolicyAgentState(TypedDict):
|
66
|
+
"""State schema for PolicyAgent workflow."""
|
67
|
+
messages: Annotated[List[BaseMessage], "conversation history"]
|
68
|
+
query: str
|
69
|
+
repo_path: str
|
70
|
+
thread_id: str
|
71
|
+
operation_type: str
|
72
|
+
scan_results: Optional[TfSecScanOutput]
|
73
|
+
policy_status: str
|
74
|
+
should_block_apply: bool
|
75
|
+
artifact_path: Optional[str]
|
76
|
+
error_message: Optional[str]
|
77
|
+
final_result: str
|
78
|
+
|
79
|
+
|
80
|
+
class PolicyAgent(AgentBase):
|
81
|
+
"""
|
82
|
+
Policy Agent for security policy enforcement in Terraform workflows.
|
83
|
+
|
84
|
+
This agent provides security policy scanning and enforcement using tfsec,
|
85
|
+
with the ability to block terraform apply operations on critical violations.
|
86
|
+
"""
|
87
|
+
|
88
|
+
def __init__(self, config_path: str = None, memory_type: str = "persistent"):
|
89
|
+
"""Initialize PolicyAgent with configuration and tools."""
|
90
|
+
self.config_path = config_path
|
91
|
+
|
92
|
+
# Load configuration
|
93
|
+
self.config = self._load_config()
|
94
|
+
|
95
|
+
# Initialize logging
|
96
|
+
self.logger = logging.getLogger(__name__)
|
97
|
+
|
98
|
+
# Initialize memory
|
99
|
+
self.memory = create_memory(memory_type=memory_type)
|
100
|
+
self.memory_adapter = LangGraphMemoryAdapter(self.memory)
|
101
|
+
|
102
|
+
# Initialize tools
|
103
|
+
self.tfsec_tool = TfSecTool(config_path=config_path)
|
104
|
+
|
105
|
+
# Build LangGraph workflow
|
106
|
+
self.workflow = self._build_workflow()
|
107
|
+
|
108
|
+
def _load_config(self) -> Dict[str, Any]:
|
109
|
+
"""Load agent configuration from YAML file."""
|
110
|
+
if self.config_path and os.path.exists(self.config_path):
|
111
|
+
with open(self.config_path, 'r') as f:
|
112
|
+
return yaml.safe_load(f)
|
113
|
+
|
114
|
+
# Default configuration
|
115
|
+
config_file = os.path.join(os.path.dirname(__file__), "config.yaml")
|
116
|
+
if os.path.exists(config_file):
|
117
|
+
with open(config_file, 'r') as f:
|
118
|
+
return yaml.safe_load(f)
|
119
|
+
|
120
|
+
# Fallback minimal configuration
|
121
|
+
return {
|
122
|
+
"policy": {
|
123
|
+
"tfsec": {
|
124
|
+
"enabled": True,
|
125
|
+
"block_on_severity": ["CRITICAL", "HIGH"],
|
126
|
+
"artifact_on_severity": ["CRITICAL", "HIGH", "MEDIUM"]
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
def _build_workflow(self) -> StateGraph:
|
132
|
+
"""Build the LangGraph workflow for policy enforcement."""
|
133
|
+
workflow = StateGraph(PolicyAgentState)
|
134
|
+
|
135
|
+
# Add nodes
|
136
|
+
workflow.add_node("planner", self._planner_llm_node)
|
137
|
+
workflow.add_node("policy_scan", self._policy_scan_node)
|
138
|
+
workflow.add_node("policy_evaluate", self._policy_evaluate_node)
|
139
|
+
workflow.add_node("policy_block", self._policy_block_node)
|
140
|
+
workflow.add_node("policy_report", self._policy_report_node)
|
141
|
+
|
142
|
+
# Add edges
|
143
|
+
workflow.set_entry_point("planner")
|
144
|
+
workflow.add_conditional_edges(
|
145
|
+
"planner",
|
146
|
+
self._route_after_planner,
|
147
|
+
{
|
148
|
+
"policy_scan": "policy_scan",
|
149
|
+
"policy_evaluate": "policy_evaluate",
|
150
|
+
"policy_block": "policy_block",
|
151
|
+
"policy_report": "policy_report",
|
152
|
+
"end": END
|
153
|
+
}
|
154
|
+
)
|
155
|
+
|
156
|
+
# Policy scan flows to evaluation
|
157
|
+
workflow.add_edge("policy_scan", "policy_evaluate")
|
158
|
+
|
159
|
+
# Policy evaluation routes to block or report
|
160
|
+
workflow.add_conditional_edges(
|
161
|
+
"policy_evaluate",
|
162
|
+
self._route_after_evaluation,
|
163
|
+
{
|
164
|
+
"policy_block": "policy_block",
|
165
|
+
"policy_report": "policy_report"
|
166
|
+
}
|
167
|
+
)
|
168
|
+
|
169
|
+
# Block and report both end
|
170
|
+
workflow.add_edge("policy_block", END)
|
171
|
+
workflow.add_edge("policy_report", END)
|
172
|
+
|
173
|
+
return workflow.compile(checkpointer=MemorySaver())
|
174
|
+
|
175
|
+
def _planner_llm_node(self, state: PolicyAgentState):
|
176
|
+
"""LLM-based planner node for routing policy operations."""
|
177
|
+
try:
|
178
|
+
query_content = state.get("query", "")
|
179
|
+
|
180
|
+
# Use simple rule-based routing for now (can be enhanced with LLM later)
|
181
|
+
route_decision = self._determine_route(query_content, state)
|
182
|
+
|
183
|
+
self.memory.add_to_conversation(
|
184
|
+
"assistant",
|
185
|
+
f"Policy planner routing to: {route_decision}",
|
186
|
+
{"agent": "policy_agent", "node": "planner", "route": route_decision}
|
187
|
+
)
|
188
|
+
|
189
|
+
return {"final_result": route_decision}
|
190
|
+
|
191
|
+
except Exception as e:
|
192
|
+
self.logger.error(f"Policy planner error: {str(e)}", exc_info=True)
|
193
|
+
return {"final_result": "end", "error_message": str(e)}
|
194
|
+
|
195
|
+
def _determine_route(self, query: str, state: PolicyAgentState) -> str:
|
196
|
+
"""Determine routing based on query content and state."""
|
197
|
+
query_lower = query.lower()
|
198
|
+
operation_type = state.get("operation_type", "scan")
|
199
|
+
|
200
|
+
if operation_type == "scan" or "scan" in query_lower:
|
201
|
+
return "policy_scan"
|
202
|
+
elif operation_type == "evaluate" or "evaluate" in query_lower:
|
203
|
+
return "policy_evaluate"
|
204
|
+
elif operation_type == "block" or "block" in query_lower:
|
205
|
+
return "policy_block"
|
206
|
+
elif "report" in query_lower or "artifact" in query_lower:
|
207
|
+
return "policy_report"
|
208
|
+
else:
|
209
|
+
return "policy_scan" # Default to scan
|
210
|
+
|
211
|
+
def _policy_scan_node(self, state: PolicyAgentState):
|
212
|
+
"""Execute tfsec security scan on the repository."""
|
213
|
+
try:
|
214
|
+
repo_path = state.get("repo_path", "")
|
215
|
+
|
216
|
+
self.logger.info(f"Starting policy scan for repository: {repo_path}")
|
217
|
+
|
218
|
+
# Configure scan input
|
219
|
+
scan_input = TfSecScanInput(
|
220
|
+
repo_path=repo_path,
|
221
|
+
output_format="json",
|
222
|
+
timeout=self.config.get("policy", {}).get("tfsec", {}).get("timeout_seconds", 120)
|
223
|
+
)
|
224
|
+
|
225
|
+
# Execute scan
|
226
|
+
scan_results = self.tfsec_tool.scan(scan_input)
|
227
|
+
|
228
|
+
if scan_results.scan_successful:
|
229
|
+
self.logger.info(f"Policy scan completed: {scan_results.total_findings} findings")
|
230
|
+
status_msg = f"Policy scan completed successfully with {scan_results.total_findings} findings"
|
231
|
+
else:
|
232
|
+
self.logger.error(f"Policy scan failed: {scan_results.error_message}")
|
233
|
+
status_msg = f"Policy scan failed: {scan_results.error_message}"
|
234
|
+
|
235
|
+
self.memory.add_to_conversation(
|
236
|
+
"assistant",
|
237
|
+
status_msg,
|
238
|
+
{
|
239
|
+
"agent": "policy_agent",
|
240
|
+
"node": "policy_scan",
|
241
|
+
"findings_count": scan_results.total_findings,
|
242
|
+
"scan_successful": scan_results.scan_successful
|
243
|
+
}
|
244
|
+
)
|
245
|
+
|
246
|
+
return {
|
247
|
+
"scan_results": scan_results,
|
248
|
+
"policy_status": "SCANNED" if scan_results.scan_successful else "ERROR"
|
249
|
+
}
|
250
|
+
|
251
|
+
except Exception as e:
|
252
|
+
error_msg = f"Policy scan node error: {str(e)}"
|
253
|
+
self.logger.error(error_msg, exc_info=True)
|
254
|
+
return {
|
255
|
+
"policy_status": "ERROR",
|
256
|
+
"error_message": error_msg
|
257
|
+
}
|
258
|
+
|
259
|
+
def _policy_evaluate_node(self, state: PolicyAgentState):
|
260
|
+
"""Evaluate scan results and determine if apply should be blocked."""
|
261
|
+
try:
|
262
|
+
scan_results = state.get("scan_results")
|
263
|
+
if not scan_results:
|
264
|
+
return {"policy_status": "ERROR", "error_message": "No scan results to evaluate"}
|
265
|
+
|
266
|
+
# Get blocking configuration
|
267
|
+
block_on_severity = self.config.get("policy", {}).get("tfsec", {}).get("block_on_severity", ["CRITICAL", "HIGH"])
|
268
|
+
|
269
|
+
# Determine if apply should be blocked
|
270
|
+
should_block = self.tfsec_tool.should_block_apply(scan_results, block_on_severity)
|
271
|
+
|
272
|
+
if should_block:
|
273
|
+
self.logger.warning(f"Policy evaluation: BLOCK - {scan_results.critical_count} critical, {scan_results.high_count} high severity findings")
|
274
|
+
policy_status = "BLOCK"
|
275
|
+
else:
|
276
|
+
self.logger.info(f"Policy evaluation: PASS - No blocking violations found")
|
277
|
+
policy_status = "PASS"
|
278
|
+
|
279
|
+
self.memory.add_to_conversation(
|
280
|
+
"assistant",
|
281
|
+
f"Policy evaluation: {policy_status} - Should block apply: {should_block}",
|
282
|
+
{
|
283
|
+
"agent": "policy_agent",
|
284
|
+
"node": "policy_evaluate",
|
285
|
+
"policy_status": policy_status,
|
286
|
+
"should_block_apply": should_block,
|
287
|
+
"critical_findings": scan_results.critical_count,
|
288
|
+
"high_findings": scan_results.high_count
|
289
|
+
}
|
290
|
+
)
|
291
|
+
|
292
|
+
return {
|
293
|
+
"policy_status": policy_status,
|
294
|
+
"should_block_apply": should_block
|
295
|
+
}
|
296
|
+
|
297
|
+
except Exception as e:
|
298
|
+
error_msg = f"Policy evaluation error: {str(e)}"
|
299
|
+
self.logger.error(error_msg, exc_info=True)
|
300
|
+
return {
|
301
|
+
"policy_status": "ERROR",
|
302
|
+
"error_message": error_msg
|
303
|
+
}
|
304
|
+
|
305
|
+
def _policy_block_node(self, state: PolicyAgentState):
|
306
|
+
"""Handle policy blocking - prevent terraform apply and create artifacts."""
|
307
|
+
try:
|
308
|
+
scan_results = state.get("scan_results")
|
309
|
+
repo_path = state.get("repo_path", "")
|
310
|
+
|
311
|
+
# Create artifact for audit trail
|
312
|
+
artifact_path = self._create_artifact(scan_results, repo_path)
|
313
|
+
|
314
|
+
block_msg = f"POLICY VIOLATION: Terraform apply blocked due to {scan_results.critical_count} critical and {scan_results.high_count} high severity policy violations"
|
315
|
+
|
316
|
+
self.logger.warning(block_msg)
|
317
|
+
self.memory.add_to_conversation(
|
318
|
+
"assistant",
|
319
|
+
block_msg,
|
320
|
+
{
|
321
|
+
"agent": "policy_agent",
|
322
|
+
"node": "policy_block",
|
323
|
+
"action": "blocked_apply",
|
324
|
+
"artifact_path": artifact_path
|
325
|
+
}
|
326
|
+
)
|
327
|
+
|
328
|
+
return {
|
329
|
+
"final_result": block_msg,
|
330
|
+
"artifact_path": artifact_path
|
331
|
+
}
|
332
|
+
|
333
|
+
except Exception as e:
|
334
|
+
error_msg = f"Policy block node error: {str(e)}"
|
335
|
+
self.logger.error(error_msg, exc_info=True)
|
336
|
+
return {
|
337
|
+
"final_result": error_msg,
|
338
|
+
"error_message": error_msg
|
339
|
+
}
|
340
|
+
|
341
|
+
def _policy_report_node(self, state: PolicyAgentState):
|
342
|
+
"""Generate policy report and artifacts."""
|
343
|
+
try:
|
344
|
+
scan_results = state.get("scan_results")
|
345
|
+
repo_path = state.get("repo_path", "")
|
346
|
+
|
347
|
+
# Create artifact for audit trail
|
348
|
+
artifact_path = self._create_artifact(scan_results, repo_path)
|
349
|
+
|
350
|
+
report_msg = f"Policy scan completed: {scan_results.total_findings} findings ({scan_results.critical_count} critical, {scan_results.high_count} high, {scan_results.medium_count} medium, {scan_results.low_count} low)"
|
351
|
+
|
352
|
+
self.logger.info(report_msg)
|
353
|
+
self.memory.add_to_conversation(
|
354
|
+
"assistant",
|
355
|
+
report_msg,
|
356
|
+
{
|
357
|
+
"agent": "policy_agent",
|
358
|
+
"node": "policy_report",
|
359
|
+
"action": "generated_report",
|
360
|
+
"artifact_path": artifact_path
|
361
|
+
}
|
362
|
+
)
|
363
|
+
|
364
|
+
return {
|
365
|
+
"final_result": report_msg,
|
366
|
+
"artifact_path": artifact_path
|
367
|
+
}
|
368
|
+
|
369
|
+
except Exception as e:
|
370
|
+
error_msg = f"Policy report node error: {str(e)}"
|
371
|
+
self.logger.error(error_msg, exc_info=True)
|
372
|
+
return {
|
373
|
+
"final_result": error_msg,
|
374
|
+
"error_message": error_msg
|
375
|
+
}
|
376
|
+
|
377
|
+
def _create_artifact(self, scan_results: TfSecScanOutput, repo_path: str) -> Optional[str]:
|
378
|
+
"""Create JSON artifact with policy findings."""
|
379
|
+
try:
|
380
|
+
# Get artifact configuration
|
381
|
+
artifacts_config = self.config.get("policy", {}).get("artifacts", {})
|
382
|
+
output_dir = artifacts_config.get("output_dir", "/workspace/.policy_findings")
|
383
|
+
|
384
|
+
# Create artifact path
|
385
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
386
|
+
filename = artifacts_config.get("json_filename", "policy_findings_{timestamp}.json").format(timestamp=timestamp)
|
387
|
+
artifact_path = os.path.join(output_dir, filename)
|
388
|
+
|
389
|
+
# Create artifact
|
390
|
+
if self.tfsec_tool.create_findings_artifact(scan_results, artifact_path):
|
391
|
+
return artifact_path
|
392
|
+
else:
|
393
|
+
self.logger.warning("Failed to create policy findings artifact")
|
394
|
+
return None
|
395
|
+
|
396
|
+
except Exception as e:
|
397
|
+
self.logger.error(f"Error creating policy artifact: {str(e)}")
|
398
|
+
return None
|
399
|
+
|
400
|
+
def _route_after_planner(self, state: PolicyAgentState) -> str:
|
401
|
+
"""Route after planner decision."""
|
402
|
+
final_result = state.get("final_result", "")
|
403
|
+
|
404
|
+
if final_result == "policy_scan":
|
405
|
+
return "policy_scan"
|
406
|
+
elif final_result == "policy_evaluate":
|
407
|
+
return "policy_evaluate"
|
408
|
+
elif final_result == "policy_block":
|
409
|
+
return "policy_block"
|
410
|
+
elif final_result == "policy_report":
|
411
|
+
return "policy_report"
|
412
|
+
else:
|
413
|
+
return "end"
|
414
|
+
|
415
|
+
def _route_after_evaluation(self, state: PolicyAgentState) -> str:
|
416
|
+
"""Route after policy evaluation."""
|
417
|
+
should_block = state.get("should_block_apply", False)
|
418
|
+
|
419
|
+
if should_block:
|
420
|
+
return "policy_block"
|
421
|
+
else:
|
422
|
+
return "policy_report"
|
423
|
+
|
424
|
+
# --- AgentBase Implementation ---
|
425
|
+
|
426
|
+
def plan(self, agent_input: PolicyAgentInput) -> Dict[str, Any]:
|
427
|
+
"""Generate plan for policy enforcement."""
|
428
|
+
return {
|
429
|
+
"operation": "policy_enforcement",
|
430
|
+
"repo_path": agent_input.repo_path,
|
431
|
+
"query": agent_input.query,
|
432
|
+
"steps": ["scan", "evaluate", "report_or_block"]
|
433
|
+
}
|
434
|
+
|
435
|
+
def run(self, agent_input: PolicyAgentInput) -> PolicyAgentOutput:
|
436
|
+
"""Execute policy enforcement workflow."""
|
437
|
+
try:
|
438
|
+
thread_id = agent_input.thread_id or str(uuid.uuid4())
|
439
|
+
|
440
|
+
# Prepare initial state
|
441
|
+
initial_state = PolicyAgentState(
|
442
|
+
messages=[HumanMessage(content=agent_input.query)],
|
443
|
+
query=agent_input.query,
|
444
|
+
repo_path=agent_input.repo_path,
|
445
|
+
thread_id=thread_id,
|
446
|
+
operation_type=agent_input.operation_type,
|
447
|
+
scan_results=None,
|
448
|
+
policy_status="PENDING",
|
449
|
+
should_block_apply=False,
|
450
|
+
artifact_path=None,
|
451
|
+
error_message=None,
|
452
|
+
final_result=""
|
453
|
+
)
|
454
|
+
|
455
|
+
# Execute workflow
|
456
|
+
config = {"configurable": {"thread_id": thread_id}}
|
457
|
+
final_state = None
|
458
|
+
|
459
|
+
for state in self.workflow.stream(initial_state, config):
|
460
|
+
final_state = state
|
461
|
+
|
462
|
+
if not final_state:
|
463
|
+
raise RuntimeError("Workflow execution failed - no final state")
|
464
|
+
|
465
|
+
# Extract results from final state
|
466
|
+
state_values = list(final_state.values())[0] if final_state else {}
|
467
|
+
scan_results = state_values.get("scan_results")
|
468
|
+
|
469
|
+
return PolicyAgentOutput(
|
470
|
+
result=state_values.get("final_result", "Policy enforcement completed"),
|
471
|
+
thread_id=thread_id,
|
472
|
+
policy_status=state_values.get("policy_status", "COMPLETED"),
|
473
|
+
findings_count=scan_results.total_findings if scan_results else 0,
|
474
|
+
critical_findings=scan_results.critical_count if scan_results else 0,
|
475
|
+
should_block_apply=state_values.get("should_block_apply", False),
|
476
|
+
artifact_path=state_values.get("artifact_path"),
|
477
|
+
error_message=state_values.get("error_message")
|
478
|
+
)
|
479
|
+
|
480
|
+
except Exception as e:
|
481
|
+
self.logger.error(f"Policy agent execution failed: {str(e)}", exc_info=True)
|
482
|
+
return PolicyAgentOutput(
|
483
|
+
result=f"Policy enforcement failed: {str(e)}",
|
484
|
+
thread_id=agent_input.thread_id or "unknown",
|
485
|
+
policy_status="ERROR",
|
486
|
+
error_message=str(e)
|
487
|
+
)
|
488
|
+
|
489
|
+
def report(self, agent_output: PolicyAgentOutput) -> str:
|
490
|
+
"""Generate human-readable report of policy enforcement results."""
|
491
|
+
report_lines = [
|
492
|
+
"=== Policy Enforcement Report ===",
|
493
|
+
f"Status: {agent_output.policy_status}",
|
494
|
+
f"Total Findings: {agent_output.findings_count}",
|
495
|
+
f"Critical Findings: {agent_output.critical_findings}",
|
496
|
+
f"Should Block Apply: {agent_output.should_block_apply}",
|
497
|
+
]
|
498
|
+
|
499
|
+
if agent_output.artifact_path:
|
500
|
+
report_lines.append(f"Artifact: {agent_output.artifact_path}")
|
501
|
+
|
502
|
+
if agent_output.error_message:
|
503
|
+
report_lines.append(f"Error: {agent_output.error_message}")
|
504
|
+
|
505
|
+
report_lines.append(f"Result: {agent_output.result}")
|
506
|
+
|
507
|
+
return "\n".join(report_lines)
|
@@ -0,0 +1,191 @@
|
|
1
|
+
"""
|
2
|
+
PolicyAgent Integration Example
|
3
|
+
|
4
|
+
This example demonstrates how the PolicyAgent integrates with the existing
|
5
|
+
multi-agent workflow to provide security policy enforcement.
|
6
|
+
"""
|
7
|
+
|
8
|
+
# Example usage in supervisor workflow:
|
9
|
+
|
10
|
+
def enhanced_terraform_workflow_with_policy_gate(repo_path: str):
|
11
|
+
"""
|
12
|
+
Enhanced terraform workflow with policy gate integration.
|
13
|
+
|
14
|
+
This shows how the PolicyAgent integrates with the existing workflow
|
15
|
+
to block terraform apply operations on critical security violations.
|
16
|
+
"""
|
17
|
+
|
18
|
+
# Step 1: Standard terraform init and plan (existing workflow)
|
19
|
+
terraform_result = terraform_agent.run(TerraformAgentInput(
|
20
|
+
query=f"terraform plan in {repo_path}",
|
21
|
+
thread_id=thread_id
|
22
|
+
))
|
23
|
+
|
24
|
+
if terraform_result.error_message:
|
25
|
+
# Handle terraform errors with existing error classification
|
26
|
+
error_tags = classify_terraform_error(terraform_result.error_message)
|
27
|
+
|
28
|
+
if "policy_block" in error_tags:
|
29
|
+
# Route to PolicyAgent for detailed policy analysis
|
30
|
+
policy_result = policy_agent.run(PolicyAgentInput(
|
31
|
+
query="evaluate security policies and determine if apply should be blocked",
|
32
|
+
repo_path=repo_path,
|
33
|
+
operation_type="evaluate"
|
34
|
+
))
|
35
|
+
|
36
|
+
if policy_result.should_block_apply:
|
37
|
+
# Block terraform apply and create GitHub issue
|
38
|
+
return create_policy_violation_issue(policy_result)
|
39
|
+
|
40
|
+
# Step 2: Pre-apply policy scan (new step)
|
41
|
+
policy_result = policy_agent.run(PolicyAgentInput(
|
42
|
+
query="scan repository for security violations before terraform apply",
|
43
|
+
repo_path=repo_path,
|
44
|
+
operation_type="scan"
|
45
|
+
))
|
46
|
+
|
47
|
+
if policy_result.should_block_apply:
|
48
|
+
# Critical violations found - block apply
|
49
|
+
logger.warning(f"Terraform apply blocked due to {policy_result.critical_findings} critical policy violations")
|
50
|
+
|
51
|
+
# Create GitHub issue with policy findings
|
52
|
+
return create_policy_blocking_issue(policy_result)
|
53
|
+
|
54
|
+
# Step 3: Continue with terraform apply (existing workflow)
|
55
|
+
if policy_result.policy_status == "PASS":
|
56
|
+
apply_result = terraform_agent.run(TerraformAgentInput(
|
57
|
+
query=f"terraform apply in {repo_path}",
|
58
|
+
thread_id=thread_id
|
59
|
+
))
|
60
|
+
|
61
|
+
# Step 4: Post-apply audit artifact (new step)
|
62
|
+
policy_agent.run(PolicyAgentInput(
|
63
|
+
query="create audit artifact with policy scan results",
|
64
|
+
repo_path=repo_path,
|
65
|
+
operation_type="report"
|
66
|
+
))
|
67
|
+
|
68
|
+
return apply_result
|
69
|
+
else:
|
70
|
+
return policy_result
|
71
|
+
|
72
|
+
|
73
|
+
def create_policy_violation_issue(policy_result):
|
74
|
+
"""Create GitHub issue for policy violations."""
|
75
|
+
issue_title = f"Security Policy Violations Detected - {policy_result.critical_findings} Critical Issues"
|
76
|
+
|
77
|
+
issue_body = f"""
|
78
|
+
## Security Policy Violations
|
79
|
+
|
80
|
+
**Status:** {policy_result.policy_status}
|
81
|
+
**Critical Findings:** {policy_result.critical_findings}
|
82
|
+
**Total Findings:** {policy_result.findings_count}
|
83
|
+
|
84
|
+
### Action Required
|
85
|
+
Terraform apply has been blocked due to critical security policy violations.
|
86
|
+
|
87
|
+
### Audit Artifact
|
88
|
+
Policy findings have been saved to: `{policy_result.artifact_path}`
|
89
|
+
|
90
|
+
### Next Steps
|
91
|
+
1. Review the policy findings in the artifact file
|
92
|
+
2. Fix the security violations
|
93
|
+
3. Re-run the terraform workflow
|
94
|
+
|
95
|
+
### Technical Details
|
96
|
+
```
|
97
|
+
{policy_result.result}
|
98
|
+
```
|
99
|
+
"""
|
100
|
+
|
101
|
+
return git_agent.run(GitAgentInput(
|
102
|
+
query=f"open issue '{issue_title}' with policy violation details",
|
103
|
+
issue_body=issue_body
|
104
|
+
))
|
105
|
+
|
106
|
+
|
107
|
+
# Example configuration integration:
|
108
|
+
|
109
|
+
POLICY_ENFORCEMENT_CONFIG = {
|
110
|
+
"policy": {
|
111
|
+
"tfsec": {
|
112
|
+
"enabled": True,
|
113
|
+
"block_on_severity": ["CRITICAL", "HIGH"],
|
114
|
+
"artifact_on_severity": ["CRITICAL", "HIGH", "MEDIUM"],
|
115
|
+
"timeout_seconds": 120
|
116
|
+
},
|
117
|
+
"artifacts": {
|
118
|
+
"output_dir": "/workspace/.policy_findings",
|
119
|
+
"json_filename": "policy_findings_{timestamp}.json"
|
120
|
+
}
|
121
|
+
},
|
122
|
+
"integration": {
|
123
|
+
"supervisor": {
|
124
|
+
"pre_apply_scan": True,
|
125
|
+
"post_apply_audit": True,
|
126
|
+
"create_github_issues": True
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
# Example routing in supervisor planner:
|
133
|
+
|
134
|
+
def enhanced_supervisor_routing():
|
135
|
+
"""
|
136
|
+
Enhanced supervisor routing with policy gate integration.
|
137
|
+
|
138
|
+
This shows how the supervisor routes requests to the PolicyAgent
|
139
|
+
based on terraform error classification and workflow state.
|
140
|
+
"""
|
141
|
+
|
142
|
+
routing_keys = {
|
143
|
+
"clone": "ROUTE_TO_CLONE",
|
144
|
+
"stack_detect": "ROUTE_TO_STACK_DETECT",
|
145
|
+
"branch_create": "ROUTE_TO_BRANCH_CREATE",
|
146
|
+
"terraform": "ROUTE_TO_TERRAFORM",
|
147
|
+
"policy_scan": "ROUTE_TO_POLICY_SCAN", # New routing
|
148
|
+
"policy_gate": "ROUTE_TO_POLICY_GATE", # New routing
|
149
|
+
"issue": "ROUTE_TO_ISSUE",
|
150
|
+
"end": "ROUTE_TO_END"
|
151
|
+
}
|
152
|
+
|
153
|
+
# Enhanced planner prompt with policy awareness
|
154
|
+
planner_prompt = f"""
|
155
|
+
You are the supervisor planner for a DevOps-in-a-Box system that manages
|
156
|
+
Terraform-based IaC deployment pipelines with security policy enforcement.
|
157
|
+
|
158
|
+
Available routing options:
|
159
|
+
1. {routing_keys["clone"]} - Clone repository
|
160
|
+
2. {routing_keys["stack_detect"]} - Detect technology stack
|
161
|
+
3. {routing_keys["branch_create"]} - Create feature branch
|
162
|
+
4. {routing_keys["terraform"]} - Execute terraform operations
|
163
|
+
5. {routing_keys["policy_scan"]} - Scan for security policy violations
|
164
|
+
6. {routing_keys["policy_gate"]} - Evaluate policy gate for apply blocking
|
165
|
+
7. {routing_keys["issue"]} - Create GitHub issue
|
166
|
+
8. {routing_keys["end"]} - End workflow
|
167
|
+
|
168
|
+
Security Policy Integration:
|
169
|
+
- Before terraform apply, always route to policy_scan for security validation
|
170
|
+
- If critical violations are found, route to policy_gate for apply blocking
|
171
|
+
- Policy violations should create GitHub issues with detailed findings
|
172
|
+
|
173
|
+
Route to the appropriate action based on the user input and workflow state.
|
174
|
+
"""
|
175
|
+
|
176
|
+
return planner_prompt
|
177
|
+
|
178
|
+
|
179
|
+
if __name__ == "__main__":
|
180
|
+
print("PolicyAgent Integration Example")
|
181
|
+
print("===============================")
|
182
|
+
print()
|
183
|
+
print("This example shows how the PolicyAgent integrates with the existing")
|
184
|
+
print("multi-agent workflow to provide security policy enforcement.")
|
185
|
+
print()
|
186
|
+
print("Key integration points:")
|
187
|
+
print("1. Pre-apply security scanning")
|
188
|
+
print("2. Policy-based apply blocking")
|
189
|
+
print("3. GitHub issue creation for violations")
|
190
|
+
print("4. Audit artifact generation")
|
191
|
+
print("5. Enhanced supervisor routing")
|