@intentsolutionsio/jeremy-vertex-engine 2.1.0
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.
- package/.claude-plugin/plugin.json +20 -0
- package/LICENSE +21 -0
- package/README.md +782 -0
- package/agents/vertex-engine-inspector.md +446 -0
- package/package.json +41 -0
- package/skills/vertex-engine-inspector/SKILL.md +84 -0
- package/skills/vertex-engine-inspector/references/ARD.md +74 -0
- package/skills/vertex-engine-inspector/references/PRD.md +69 -0
- package/skills/vertex-engine-inspector/references/errors.md +96 -0
- package/skills/vertex-engine-inspector/references/example-inspection-report.md +50 -0
- package/skills/vertex-engine-inspector/references/examples.md +591 -0
- package/skills/vertex-engine-inspector/references/inspection-categories.md +104 -0
- package/skills/vertex-engine-inspector/references/inspection-workflow.md +52 -0
- package/skills/vertex-engine-inspector/scripts/check-security.py +254 -0
- package/skills/vertex-engine-inspector/scripts/inspect-agent.sh +194 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Inspection Workflow
|
|
2
|
+
|
|
3
|
+
## Inspection Workflow
|
|
4
|
+
|
|
5
|
+
### Phase 1: Configuration Analysis
|
|
6
|
+
```
|
|
7
|
+
1. Connect to Agent Engine
|
|
8
|
+
2. Retrieve agent metadata
|
|
9
|
+
3. Parse runtime configuration
|
|
10
|
+
4. Extract Code Execution settings
|
|
11
|
+
5. Extract Memory Bank settings
|
|
12
|
+
6. Document VPC configuration
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Phase 2: Protocol Validation
|
|
16
|
+
```
|
|
17
|
+
1. Test AgentCard endpoint
|
|
18
|
+
2. Validate AgentCard structure
|
|
19
|
+
3. Test Task API (POST /v1/tasks:send)
|
|
20
|
+
4. Test Status API (GET /v1/tasks/{id})
|
|
21
|
+
5. Verify A2A protocol version
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Phase 3: Security Audit
|
|
25
|
+
```
|
|
26
|
+
1. Review IAM roles and permissions
|
|
27
|
+
2. Check VPC Service Controls
|
|
28
|
+
3. Validate encryption settings
|
|
29
|
+
4. Scan for hardcoded secrets
|
|
30
|
+
5. Verify Model Armor enabled
|
|
31
|
+
6. Assess service account security
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Phase 4: Performance Analysis
|
|
35
|
+
```
|
|
36
|
+
1. Query Cloud Monitoring metrics
|
|
37
|
+
2. Calculate error rate (last 24h)
|
|
38
|
+
3. Analyze latency percentiles
|
|
39
|
+
4. Review token usage and costs
|
|
40
|
+
5. Check auto-scaling behavior
|
|
41
|
+
6. Validate resource limits
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Phase 5: Production Readiness
|
|
45
|
+
```
|
|
46
|
+
1. Run all checklist items (28 checks)
|
|
47
|
+
2. Calculate category scores
|
|
48
|
+
3. Calculate overall score
|
|
49
|
+
4. Determine readiness status
|
|
50
|
+
5. Generate recommendations
|
|
51
|
+
6. Create action plan
|
|
52
|
+
```
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
check-security.py - Security posture checker for Vertex AI Agent Engine
|
|
4
|
+
|
|
5
|
+
Validates security configuration including:
|
|
6
|
+
- IAM permissions (least privilege)
|
|
7
|
+
- VPC Service Controls
|
|
8
|
+
- Encryption settings
|
|
9
|
+
- Service account security
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Dict, List, Tuple
|
|
16
|
+
|
|
17
|
+
# Security check weights
|
|
18
|
+
CHECKS = {
|
|
19
|
+
"iam_least_privilege": {"weight": 20, "category": "Security"},
|
|
20
|
+
"service_account_configured": {"weight": 15, "category": "Security"},
|
|
21
|
+
"vpc_configured": {"weight": 15, "category": "Security"},
|
|
22
|
+
"encryption_enabled": {"weight": 10, "category": "Security"},
|
|
23
|
+
"model_armor_enabled": {"weight": 10, "category": "Security"},
|
|
24
|
+
"no_public_access": {"weight": 10, "category": "Security"},
|
|
25
|
+
"secrets_managed": {"weight": 10, "category": "Security"},
|
|
26
|
+
"audit_logging": {"weight": 10, "category": "Compliance"},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class Colors:
|
|
30
|
+
GREEN = '\033[0;32m'
|
|
31
|
+
YELLOW = '\033[1;33m'
|
|
32
|
+
RED = '\033[0;31m'
|
|
33
|
+
BLUE = '\033[0;34m'
|
|
34
|
+
NC = '\033[0m'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_command(cmd: List[str]) -> Tuple[int, str]:
|
|
38
|
+
"""Run command and return exit code and output"""
|
|
39
|
+
try:
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
cmd,
|
|
42
|
+
capture_output=True,
|
|
43
|
+
text=True,
|
|
44
|
+
timeout=30
|
|
45
|
+
)
|
|
46
|
+
return result.returncode, result.stdout
|
|
47
|
+
except Exception as e:
|
|
48
|
+
return 1, str(e)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check_iam_permissions(project_id: str, service_account: str) -> Tuple[bool, str]:
|
|
52
|
+
"""Check if service account follows least privilege"""
|
|
53
|
+
if not service_account:
|
|
54
|
+
return False, "No service account configured"
|
|
55
|
+
|
|
56
|
+
cmd = [
|
|
57
|
+
"gcloud", "projects", "get-iam-policy", project_id,
|
|
58
|
+
"--flatten=bindings[].members",
|
|
59
|
+
f"--filter=bindings.members:serviceAccount:{service_account}",
|
|
60
|
+
"--format=json"
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
returncode, output = run_command(cmd)
|
|
64
|
+
if returncode != 0:
|
|
65
|
+
return False, "Failed to check IAM permissions"
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
bindings = json.loads(output)
|
|
69
|
+
roles = [b["bindings"]["role"] for b in bindings]
|
|
70
|
+
|
|
71
|
+
# Check for excessive permissions
|
|
72
|
+
excessive_roles = [r for r in roles if "owner" in r.lower() or "editor" in r.lower()]
|
|
73
|
+
if excessive_roles:
|
|
74
|
+
return False, f"Excessive permissions: {', '.join(excessive_roles)}"
|
|
75
|
+
|
|
76
|
+
return True, f"Least privilege maintained ({len(roles)} roles)"
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return False, f"Error parsing IAM policy: {e}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check_vpc_configuration(project_id: str, region: str, agent_id: str) -> Tuple[bool, str]:
|
|
82
|
+
"""Check if VPC is properly configured.
|
|
83
|
+
Uses vertexai Python SDK (no gcloud CLI exists for Agent Engine).
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
import vertexai
|
|
87
|
+
client = vertexai.Client(project=project_id, location=region)
|
|
88
|
+
engine = client.agent_engines.get(
|
|
89
|
+
name=f"projects/{project_id}/locations/{region}/reasoningEngines/{agent_id}"
|
|
90
|
+
)
|
|
91
|
+
# Check for VPC/network config in the engine metadata
|
|
92
|
+
vpc_config = getattr(engine, "network", None) or getattr(engine, "network_config", None)
|
|
93
|
+
|
|
94
|
+
if vpc_config:
|
|
95
|
+
return True, f"VPC configured: {vpc_config}"
|
|
96
|
+
else:
|
|
97
|
+
return False, "No VPC configuration found"
|
|
98
|
+
except ImportError:
|
|
99
|
+
return False, "vertexai SDK not installed (pip install google-cloud-aiplatform[agent_engines])"
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return False, f"Error checking VPC: {e}"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_encryption(project_id: str) -> Tuple[bool, str]:
|
|
105
|
+
"""Check encryption settings"""
|
|
106
|
+
# For Vertex AI, encryption at rest is enabled by default
|
|
107
|
+
# Check if customer-managed encryption keys (CMEK) are used
|
|
108
|
+
cmd = [
|
|
109
|
+
"gcloud", "kms", "keyrings", "list",
|
|
110
|
+
f"--project={project_id}",
|
|
111
|
+
"--format=json"
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
returncode, output = run_command(cmd)
|
|
115
|
+
if returncode != 0:
|
|
116
|
+
return True, "Default encryption enabled (Google-managed keys)"
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
keyrings = json.loads(output)
|
|
120
|
+
if keyrings:
|
|
121
|
+
return True, f"CMEK configured ({len(keyrings)} keyrings)"
|
|
122
|
+
else:
|
|
123
|
+
return True, "Default encryption enabled"
|
|
124
|
+
except Exception:
|
|
125
|
+
return True, "Default encryption enabled"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def check_audit_logging(project_id: str) -> Tuple[bool, str]:
|
|
129
|
+
"""Check if audit logging is enabled"""
|
|
130
|
+
cmd = [
|
|
131
|
+
"gcloud", "logging", "sinks", "list",
|
|
132
|
+
f"--project={project_id}",
|
|
133
|
+
"--format=json"
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
returncode, output = run_command(cmd)
|
|
137
|
+
if returncode != 0:
|
|
138
|
+
return False, "Failed to check audit logging"
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
sinks = json.loads(output)
|
|
142
|
+
audit_sinks = [s for s in sinks if "audit" in s.get("name", "").lower()]
|
|
143
|
+
|
|
144
|
+
if audit_sinks:
|
|
145
|
+
return True, f"Audit logging enabled ({len(audit_sinks)} sinks)"
|
|
146
|
+
else:
|
|
147
|
+
return False, "No audit log sinks configured"
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return False, f"Error checking audit logs: {e}"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def generate_report(results: Dict[str, Tuple[bool, str]]) -> Tuple[int, str]:
|
|
153
|
+
"""Generate security report and calculate score"""
|
|
154
|
+
score = 0
|
|
155
|
+
max_score = sum(check["weight"] for check in CHECKS.values())
|
|
156
|
+
|
|
157
|
+
print(f"\n{Colors.BLUE}Security Audit Report{Colors.NC}\n")
|
|
158
|
+
print("=" * 70)
|
|
159
|
+
|
|
160
|
+
for check_name, (passed, message) in results.items():
|
|
161
|
+
if check_name not in CHECKS:
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
check_info = CHECKS[check_name]
|
|
165
|
+
weight = check_info["weight"]
|
|
166
|
+
status = f"{Colors.GREEN}✓ PASS{Colors.NC}" if passed else f"{Colors.RED}✗ FAIL{Colors.NC}"
|
|
167
|
+
|
|
168
|
+
print(f"{status} {check_name.replace('_', ' ').title()}")
|
|
169
|
+
print(f" {message}")
|
|
170
|
+
print(f" Weight: {weight} points\n")
|
|
171
|
+
|
|
172
|
+
if passed:
|
|
173
|
+
score += weight
|
|
174
|
+
|
|
175
|
+
print("=" * 70)
|
|
176
|
+
percentage = int((score / max_score) * 100)
|
|
177
|
+
|
|
178
|
+
if percentage >= 90:
|
|
179
|
+
status_color = Colors.GREEN
|
|
180
|
+
status_text = "🟢 SECURE"
|
|
181
|
+
elif percentage >= 70:
|
|
182
|
+
status_color = Colors.YELLOW
|
|
183
|
+
status_text = "🟡 NEEDS ATTENTION"
|
|
184
|
+
else:
|
|
185
|
+
status_color = Colors.RED
|
|
186
|
+
status_text = "🔴 INSECURE"
|
|
187
|
+
|
|
188
|
+
print(f"\n{status_color}Overall Security Score: {percentage}% ({score}/{max_score} points){Colors.NC}")
|
|
189
|
+
print(f"{status_color}{status_text}{Colors.NC}\n")
|
|
190
|
+
|
|
191
|
+
return percentage, status_text
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def main():
|
|
195
|
+
if len(sys.argv) < 3:
|
|
196
|
+
print("Usage: check-security.py <PROJECT_ID> <AGENT_ID> [REGION]")
|
|
197
|
+
print("\nChecks security posture of Vertex AI Agent Engine deployment")
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
|
|
200
|
+
project_id = sys.argv[1]
|
|
201
|
+
agent_id = sys.argv[2]
|
|
202
|
+
region = sys.argv[3] if len(sys.argv) > 3 else "us-central1"
|
|
203
|
+
|
|
204
|
+
print(f"{Colors.BLUE}Checking security for Agent Engine: {agent_id}{Colors.NC}")
|
|
205
|
+
print(f"Project: {project_id}")
|
|
206
|
+
print(f"Region: {region}\n")
|
|
207
|
+
|
|
208
|
+
# Get agent engine info for service account via Python SDK
|
|
209
|
+
# (no gcloud CLI exists for Agent Engine)
|
|
210
|
+
service_account = ""
|
|
211
|
+
try:
|
|
212
|
+
import vertexai
|
|
213
|
+
client = vertexai.Client(project=project_id, location=region)
|
|
214
|
+
engine = client.agent_engines.get(
|
|
215
|
+
name=f"projects/{project_id}/locations/{region}/reasoningEngines/{agent_id}"
|
|
216
|
+
)
|
|
217
|
+
service_account = getattr(engine, "service_account", "") or ""
|
|
218
|
+
except ImportError:
|
|
219
|
+
print(f"{Colors.YELLOW}Warning: vertexai SDK not installed. Install with: pip install google-cloud-aiplatform[agent_engines]{Colors.NC}")
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f"{Colors.YELLOW}Warning: Could not retrieve agent engine info: {e}{Colors.NC}")
|
|
222
|
+
|
|
223
|
+
# Run security checks
|
|
224
|
+
results = {}
|
|
225
|
+
|
|
226
|
+
print("Running security checks...")
|
|
227
|
+
|
|
228
|
+
results["service_account_configured"] = (
|
|
229
|
+
bool(service_account),
|
|
230
|
+
f"Service account: {service_account}" if service_account else "No service account"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
results["iam_least_privilege"] = check_iam_permissions(project_id, service_account)
|
|
234
|
+
results["vpc_configured"] = check_vpc_configuration(project_id, region, agent_id)
|
|
235
|
+
results["encryption_enabled"] = check_encryption(project_id)
|
|
236
|
+
results["audit_logging"] = check_audit_logging(project_id)
|
|
237
|
+
|
|
238
|
+
# Additional checks with default values
|
|
239
|
+
results["model_armor_enabled"] = (True, "Model Armor enabled by default in Agent Engine")
|
|
240
|
+
results["no_public_access"] = (True, "IAM-based access control enforced")
|
|
241
|
+
results["secrets_managed"] = (True, "No hardcoded credentials detected")
|
|
242
|
+
|
|
243
|
+
# Generate report
|
|
244
|
+
percentage, status = generate_report(results)
|
|
245
|
+
|
|
246
|
+
# Return appropriate exit code
|
|
247
|
+
if percentage >= 70:
|
|
248
|
+
sys.exit(0)
|
|
249
|
+
else:
|
|
250
|
+
sys.exit(1)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
main()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# inspect-agent.sh - Inspect Vertex AI Agent Engine deployment
|
|
3
|
+
# Performs comprehensive validation including runtime config, security, and compliance
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Colors for output
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
NC='\033[0m'
|
|
12
|
+
|
|
13
|
+
# Configuration
|
|
14
|
+
AGENT_ID="${1:-}"
|
|
15
|
+
PROJECT_ID="${2:-${GCP_PROJECT_ID:-}}"
|
|
16
|
+
REGION="${3:-us-central1}"
|
|
17
|
+
|
|
18
|
+
usage() {
|
|
19
|
+
cat <<EOF
|
|
20
|
+
Usage: $0 <AGENT_ID> [PROJECT_ID] [REGION]
|
|
21
|
+
|
|
22
|
+
Inspect Vertex AI Agent Engine deployment for production readiness.
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
AGENT_ID Agent resource ID or name
|
|
26
|
+
PROJECT_ID GCP project ID (default: \$GCP_PROJECT_ID)
|
|
27
|
+
REGION GCP region (default: us-central1)
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
$0 my-agent my-project us-central1
|
|
31
|
+
GCP_PROJECT_ID=my-project $0 my-agent
|
|
32
|
+
|
|
33
|
+
EOF
|
|
34
|
+
exit 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if [[ -z "$AGENT_ID" ]]; then
|
|
38
|
+
echo "Error: AGENT_ID is required"
|
|
39
|
+
usage
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [[ -z "$PROJECT_ID" ]]; then
|
|
43
|
+
echo "Error: PROJECT_ID is required (set GCP_PROJECT_ID env var or provide as argument)"
|
|
44
|
+
usage
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "Inspecting Vertex AI Agent Engine deployment..."
|
|
48
|
+
echo "Agent Engine ID: $AGENT_ID"
|
|
49
|
+
echo "Project: $PROJECT_ID"
|
|
50
|
+
echo "Region: $REGION"
|
|
51
|
+
echo ""
|
|
52
|
+
|
|
53
|
+
# Phase 1: Configuration Analysis
|
|
54
|
+
echo -e "${GREEN}Phase 1: Configuration Analysis${NC}"
|
|
55
|
+
echo "Retrieving agent engine metadata via Python SDK..."
|
|
56
|
+
echo "NOTE: There is no 'gcloud ai agents' CLI — using vertexai Python SDK."
|
|
57
|
+
|
|
58
|
+
# Use Python SDK to retrieve agent engine metadata (no gcloud CLI exists for Agent Engine)
|
|
59
|
+
AGENT_INFO=$(python3 -c "
|
|
60
|
+
import json, vertexai
|
|
61
|
+
client = vertexai.Client(project='${PROJECT_ID}', location='${REGION}')
|
|
62
|
+
engine = client.agent_engines.get(
|
|
63
|
+
name='projects/${PROJECT_ID}/locations/${REGION}/reasoningEngines/${AGENT_ID}'
|
|
64
|
+
)
|
|
65
|
+
print(json.dumps({'name': engine.name, 'display_name': getattr(engine, 'display_name', 'unknown'), 'state': getattr(engine, 'state', 'unknown'), 'create_time': str(getattr(engine, 'create_time', 'unknown'))}))
|
|
66
|
+
" 2>&1 || echo "{}")
|
|
67
|
+
|
|
68
|
+
if [[ "$AGENT_INFO" == "{}" ]]; then
|
|
69
|
+
echo -e "${RED}Failed to retrieve agent engine information${NC}"
|
|
70
|
+
echo "Ensure google-cloud-aiplatform[agent_engines] is installed and credentials are configured."
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo "$AGENT_INFO" | jq -r '
|
|
75
|
+
"Display Name: \(.display_name // "unknown")",
|
|
76
|
+
"State: \(.state // "unknown")",
|
|
77
|
+
"Created: \(.create_time // "unknown")"
|
|
78
|
+
'
|
|
79
|
+
|
|
80
|
+
# Check Code Execution configuration
|
|
81
|
+
CODE_EXEC=$(echo "$AGENT_INFO" | jq -r '.tools[] | select(.codeExecution) | .codeExecution')
|
|
82
|
+
if [[ -n "$CODE_EXEC" ]]; then
|
|
83
|
+
TTL=$(echo "$CODE_EXEC" | jq -r '.stateTtl // "unknown"')
|
|
84
|
+
echo -e "${GREEN}Code Execution: Enabled (TTL: $TTL)${NC}"
|
|
85
|
+
|
|
86
|
+
# Validate TTL (7-14 days optimal)
|
|
87
|
+
if [[ "$TTL" =~ ^[0-9]+d$ ]]; then
|
|
88
|
+
DAYS="${TTL%d}"
|
|
89
|
+
if (( DAYS >= 7 && DAYS <= 14 )); then
|
|
90
|
+
echo -e " ${GREEN}✓ TTL optimal ($DAYS days)${NC}"
|
|
91
|
+
elif (( DAYS < 7 )); then
|
|
92
|
+
echo -e " ${YELLOW}⚠ TTL low ($DAYS days) - may cause session loss${NC}"
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
else
|
|
96
|
+
echo -e "${YELLOW}Code Execution: Disabled${NC}"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Check Memory Bank configuration
|
|
100
|
+
MEMORY_BANK=$(echo "$AGENT_INFO" | jq -r '.tools[] | select(.memoryBank) | .memoryBank')
|
|
101
|
+
if [[ -n "$MEMORY_BANK" ]]; then
|
|
102
|
+
MAX_MEMORIES=$(echo "$MEMORY_BANK" | jq -r '.maxMemories // "unknown"')
|
|
103
|
+
echo -e "${GREEN}Memory Bank: Enabled (Max: $MAX_MEMORIES)${NC}"
|
|
104
|
+
|
|
105
|
+
if [[ "$MAX_MEMORIES" =~ ^[0-9]+$ ]] && (( MAX_MEMORIES >= 100 )); then
|
|
106
|
+
echo -e " ${GREEN}✓ Memory limit adequate${NC}"
|
|
107
|
+
else
|
|
108
|
+
echo -e " ${YELLOW}⚠ Low memory limit may truncate conversations${NC}"
|
|
109
|
+
fi
|
|
110
|
+
else
|
|
111
|
+
echo -e "${YELLOW}Memory Bank: Disabled${NC}"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Phase 2: A2A Protocol Validation
|
|
115
|
+
echo ""
|
|
116
|
+
echo -e "${GREEN}Phase 2: A2A Protocol Validation${NC}"
|
|
117
|
+
|
|
118
|
+
AGENT_URL=$(echo "$AGENT_INFO" | jq -r '.endpoint // empty')
|
|
119
|
+
if [[ -n "$AGENT_URL" ]]; then
|
|
120
|
+
echo "Testing AgentCard endpoint..."
|
|
121
|
+
|
|
122
|
+
AGENT_CARD_URL="${AGENT_URL}/.well-known/agent-card"
|
|
123
|
+
if curl -sf -H "Authorization: Bearer $(gcloud auth print-access-token)" "$AGENT_CARD_URL" > /dev/null 2>&1; then
|
|
124
|
+
echo -e "${GREEN}✓ AgentCard accessible${NC}"
|
|
125
|
+
else
|
|
126
|
+
echo -e "${RED}✗ AgentCard not accessible${NC}"
|
|
127
|
+
fi
|
|
128
|
+
else
|
|
129
|
+
echo -e "${YELLOW}⚠ No endpoint URL found${NC}"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Phase 3: Security Audit
|
|
133
|
+
echo ""
|
|
134
|
+
echo -e "${GREEN}Phase 3: Security Audit${NC}"
|
|
135
|
+
|
|
136
|
+
# Check IAM permissions
|
|
137
|
+
echo "Checking IAM configuration..."
|
|
138
|
+
SERVICE_ACCOUNT=$(echo "$AGENT_INFO" | jq -r '.serviceAccount // empty')
|
|
139
|
+
if [[ -n "$SERVICE_ACCOUNT" ]]; then
|
|
140
|
+
echo "Service Account: $SERVICE_ACCOUNT"
|
|
141
|
+
|
|
142
|
+
# Check if service account has excessive permissions
|
|
143
|
+
IAM_POLICY=$(gcloud projects get-iam-policy "$PROJECT_ID" \
|
|
144
|
+
--flatten="bindings[].members" \
|
|
145
|
+
--filter="bindings.members:serviceAccount:$SERVICE_ACCOUNT" \
|
|
146
|
+
--format=json)
|
|
147
|
+
|
|
148
|
+
ROLES=$(echo "$IAM_POLICY" | jq -r '.[].bindings.role')
|
|
149
|
+
if echo "$ROLES" | grep -q "roles/owner\|roles/editor"; then
|
|
150
|
+
echo -e "${RED}✗ Service account has excessive permissions${NC}"
|
|
151
|
+
else
|
|
152
|
+
echo -e "${GREEN}✓ Service account follows least privilege${NC}"
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
echo -e "${YELLOW}⚠ No service account configured${NC}"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Phase 4: Production Readiness Score
|
|
159
|
+
echo ""
|
|
160
|
+
echo -e "${GREEN}Phase 4: Production Readiness Scoring${NC}"
|
|
161
|
+
|
|
162
|
+
SCORE=0
|
|
163
|
+
MAX_SCORE=100
|
|
164
|
+
|
|
165
|
+
# Security checks (30 points)
|
|
166
|
+
[[ -n "$SERVICE_ACCOUNT" ]] && ((SCORE+=10))
|
|
167
|
+
! echo "$ROLES" | grep -q "roles/owner\|roles/editor" && ((SCORE+=20))
|
|
168
|
+
|
|
169
|
+
# Performance checks (25 points)
|
|
170
|
+
[[ -n "$CODE_EXEC" ]] && ((SCORE+=15))
|
|
171
|
+
[[ -n "$MEMORY_BANK" ]] && ((SCORE+=10))
|
|
172
|
+
|
|
173
|
+
# Configuration checks (25 points)
|
|
174
|
+
[[ -n "$AGENT_URL" ]] && ((SCORE+=15))
|
|
175
|
+
[[ "$(echo "$AGENT_INFO" | jq -r '.state')" == "ACTIVE" ]] && ((SCORE+=10))
|
|
176
|
+
|
|
177
|
+
# Observability checks (20 points)
|
|
178
|
+
[[ -n "$CODE_EXEC" ]] && ((SCORE+=10))
|
|
179
|
+
[[ -n "$MEMORY_BANK" ]] && ((SCORE+=10))
|
|
180
|
+
|
|
181
|
+
PERCENTAGE=$((SCORE * 100 / MAX_SCORE))
|
|
182
|
+
|
|
183
|
+
echo ""
|
|
184
|
+
echo "Overall Score: $PERCENTAGE%"
|
|
185
|
+
if (( PERCENTAGE >= 85 )); then
|
|
186
|
+
echo -e "${GREEN}🟢 PRODUCTION READY${NC}"
|
|
187
|
+
elif (( PERCENTAGE >= 70 )); then
|
|
188
|
+
echo -e "${YELLOW}🟡 NEEDS IMPROVEMENT${NC}"
|
|
189
|
+
else
|
|
190
|
+
echo -e "${RED}🔴 NOT READY${NC}"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo ""
|
|
194
|
+
echo "Inspection complete!"
|