Skip to content

Commit cc3b31a

Browse files
committed
Added AgentCore Gateway IAM Policy support
1 parent c952153 commit cc3b31a

5 files changed

Lines changed: 166 additions & 0 deletions

File tree

apps/java-spring-ai-agents/backoffice/expense/ExpenseService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,13 @@ public Expense getExpense(String expenseReference) {
7777
.items().stream().findFirst()
7878
.orElseThrow(() -> new ResourceNotFoundException("Expense", expenseReference));
7979
}
80+
81+
public Expense deleteExpense(String expenseReference) {
82+
Expense expense = getExpense(expenseReference);
83+
if (expense.getStatus() != Expense.ExpenseStatus.DRAFT) {
84+
throw new InvalidOperationException("Only draft expenses can be deleted");
85+
}
86+
dynamoDbTemplate.delete(expense);
87+
return expense;
88+
}
8089
}

apps/java-spring-ai-agents/backoffice/tools/ExpenseTools.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,10 @@ public Expense submitExpense(
6161
@ToolParam(description = "Expense reference (EXP-XXXXXXXX)") String expenseReference) {
6262
return service.submitExpense(expenseReference);
6363
}
64+
65+
@Tool(description = "Delete a draft expense")
66+
public Expense deleteExpense(
67+
@ToolParam(description = "Expense reference (EXP-XXXXXXXX)") String expenseReference) {
68+
return service.deleteExpense(expenseReference);
69+
}
6470
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "=============================================="
5+
echo "13-mcp-policy.sh - MCP Gateway Policy Deployment"
6+
echo "=============================================="
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
10+
source ~/environment/.envrc 2>/dev/null || true
11+
12+
if [ -z "${GATEWAY_ID}" ]; then
13+
echo "Error: Missing GATEWAY_ID. Run 06-mcp-gateway.sh first."
14+
exit 1
15+
fi
16+
17+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --no-cli-pager)
18+
AWS_REGION=$(aws configure get region --no-cli-pager)
19+
GATEWAY_ARN="arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:gateway/${GATEWAY_ID}"
20+
21+
echo "Gateway: ${GATEWAY_ID}"
22+
23+
VENV_DIR="/tmp/policy_venv"
24+
[ ! -d "${VENV_DIR}" ] && python3 -m venv "${VENV_DIR}" && "${VENV_DIR}/bin/pip" install -q boto3
25+
26+
"${VENV_DIR}/bin/python3" << EOF
27+
import boto3, time, secrets, json
28+
29+
client = boto3.client('bedrock-agentcore-control', region_name='${AWS_REGION}')
30+
iam = boto3.client('iam', region_name='${AWS_REGION}')
31+
32+
GATEWAY_ARN = "${GATEWAY_ARN}"
33+
ACCOUNT_ID = "${ACCOUNT_ID}"
34+
35+
# Get or create policy engine
36+
engines = client.list_policy_engines()
37+
existing = [e for e in engines.get('policyEngines', []) if e['name'].startswith('BackofficePolicyEngine') and e['status'] == 'ACTIVE']
38+
if existing:
39+
ENGINE_ID = existing[0]['policyEngineId']
40+
else:
41+
result = client.create_policy_engine(name=f"BackofficePolicyEngine_{secrets.token_hex(5)}")
42+
ENGINE_ID = result['policyEngineId']
43+
while client.get_policy_engine(policyEngineId=ENGINE_ID)['status'] != 'ACTIVE':
44+
time.sleep(3)
45+
print(f"Policy Engine: {ENGINE_ID}")
46+
47+
# Cleanup
48+
for p in client.list_policies(policyEngineId=ENGINE_ID).get('policies', []):
49+
try: client.delete_policy(policyEngineId=ENGINE_ID, policyId=p['policyId']); time.sleep(1)
50+
except: pass
51+
52+
# Try combined policy first
53+
COMBINED = f'''forbid(
54+
principal,
55+
action in [
56+
AgentCore::Action::"backoffice___cancelTrip",
57+
AgentCore::Action::"backoffice___deleteExpense"
58+
],
59+
resource == AgentCore::Gateway::"{GATEWAY_ARN}"
60+
);'''
61+
62+
print("Creating forbid policy...")
63+
result = client.create_policy(
64+
policyEngineId=ENGINE_ID,
65+
name="ForbidDangerousOperations",
66+
definition={'cedar': {'statement': COMBINED}},
67+
validationMode='IGNORE_ALL_FINDINGS'
68+
)
69+
70+
success = False
71+
for _ in range(15):
72+
p = client.get_policy(policyEngineId=ENGINE_ID, policyId=result['policyId'])
73+
if p['status'] == 'ACTIVE':
74+
print(" ✅ Combined policy active")
75+
success = True
76+
break
77+
elif 'FAILED' in p['status']:
78+
print(" ⚠️ Combined failed, creating separate policies...")
79+
client.delete_policy(policyEngineId=ENGINE_ID, policyId=result['policyId'])
80+
break
81+
time.sleep(2)
82+
83+
# Fallback to separate policies
84+
if not success:
85+
for action in ["cancelTrip", "deleteExpense"]:
86+
policy = f'''forbid(
87+
principal,
88+
action == AgentCore::Action::"backoffice___{action}",
89+
resource == AgentCore::Gateway::"{GATEWAY_ARN}"
90+
);'''
91+
result = client.create_policy(
92+
policyEngineId=ENGINE_ID,
93+
name=f"Forbid{action}",
94+
definition={'cedar': {'statement': policy}},
95+
validationMode='IGNORE_ALL_FINDINGS'
96+
)
97+
for _ in range(10):
98+
p = client.get_policy(policyEngineId=ENGINE_ID, policyId=result['policyId'])
99+
if p['status'] == 'ACTIVE':
100+
print(f" ✅ Forbid{action}: active")
101+
break
102+
elif 'FAILED' in p['status']:
103+
print(f" ⚠️ Forbid{action}: failed (tool not in schema)")
104+
break
105+
time.sleep(2)
106+
107+
# IAM
108+
iam.put_role_policy(
109+
RoleName='mcp-gateway-role',
110+
PolicyName='PolicyEngineAccess',
111+
PolicyDocument=json.dumps({
112+
"Version": "2012-10-17",
113+
"Statement": [{"Effect": "Allow", "Action": [
114+
"bedrock-agentcore:GetPolicyEngine", "bedrock-agentcore:IsAuthorized",
115+
"bedrock-agentcore:AuthorizeAction", "bedrock-agentcore:AuthorizeActions"
116+
], "Resource": f"arn:aws:bedrock-agentcore:${AWS_REGION}:{ACCOUNT_ID}:*"}]
117+
})
118+
)
119+
120+
# Attach to gateway
121+
gateway = client.get_gateway(gatewayIdentifier="${GATEWAY_ID}")
122+
ENGINE_ARN = f"arn:aws:bedrock-agentcore:${AWS_REGION}:{ACCOUNT_ID}:policy-engine/{ENGINE_ID}"
123+
client.update_gateway(
124+
gatewayIdentifier="${GATEWAY_ID}",
125+
name=gateway['name'], roleArn=gateway['roleArn'],
126+
protocolType=gateway['protocolType'], authorizerType=gateway['authorizerType'],
127+
policyEngineConfiguration={'arn': ENGINE_ARN, 'mode': 'ENFORCE'}
128+
)
129+
while client.get_gateway(gatewayIdentifier="${GATEWAY_ID}")['status'] != 'READY':
130+
time.sleep(2)
131+
print("✅ Gateway ready")
132+
EOF
133+
134+
echo ""
135+
echo "Done! Forbidden: cancelTrip, deleteExpense"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Backoffice MCP Gateway Policy
2+
// Forbid dangerous operations - everything else allowed by default
3+
4+
forbid(
5+
principal,
6+
action in [
7+
AgentCore::Action::"backoffice___cancelTrip",
8+
AgentCore::Action::"backoffice___deleteExpense"
9+
],
10+
resource == AgentCore::Gateway::"${GATEWAY_ARN}"
11+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM amazoncorretto:21-alpine
2+
WORKDIR /app
3+
COPY target/backoffice-0.0.1-SNAPSHOT.jar app.jar
4+
EXPOSE 8080
5+
ENTRYPOINT ["java", "-jar", "app.jar"]

0 commit comments

Comments
 (0)